SenseNova Loop-9B-Preview 本地跑,显存和量化档位先对上号

文章导读
SenseNova Loop-9B-Preview 在本地能否跑起来,通常不看参数量这个数字本身,而看三件事能否对上:权重以什么精度加载、KV 缓存和图像张量还要预留多少余量、加载过程与首次前向的真实显存峰值落在哪一档。9B 参数量在 fp16 下权重大约 18GB 量级,int8 约 9GB,int4 约 4.5GB,这些都只是算出来的数,真正决定会不会 OOM 的还有上下文长度、输入图片尺寸和
📋 目录
  1. 一 在模型目录里确认参数量、层数与权重位宽
  2. 二 把权重显存与 KV 缓存、图像张量分开估
  3. 三 用显存采样命令看加载过程的峰值
  4. 四 跑一次最小推理并记录耗时和显存
  5. 五 显存不够时的三个降级动作及各自代价
A A

SenseNova Loop-9B-Preview 在本地能否跑起来,通常不看参数量这个数字本身,而看三件事能否对上:权重以什么精度加载、KV 缓存和图像张量还要预留多少余量、加载过程与首次前向的真实显存峰值落在哪一档。9B 参数量在 fp16 下权重大约 18GB 量级,int8 约 9GB,int4 约 4.5GB,这些都只是算出来的数,真正决定会不会 OOM 的还有上下文长度、输入图片尺寸和框架的临时缓冲。可行的顺序是:先在模型目录里读出 dtype 与参数量,再按公式分项估算,然后用显存采样命令看峰值,最后跑一次最小推理确认这一档能跑通。

9B 级模型选量化档位,先算权重显存(参数量×每参数字节),再把 KV 缓存与图像张量单独加进去,用 nvidia-smi 采样看“加载中”和“首次前向”两个峰值点。fp16 适合显存充裕、要保精度的场景;int8 是多数单卡本地推理的稳妥起点;int4 只在小显存且能接受画质和速度取舍时用。估算值必须用本机采样结果校准,未在本机验证前不要把纸面数字当结论。

在模型目录里确认参数量、层数与权重位宽

动手前先把目录看清。权重文件的总字节数除以每参数字节数,能反推参数量级;配置文件里的 dtype 和量化字段,决定加载时按几字节存每个参数。这两件事都要以你实际拉下来的仓库为准,不同发布形态(原始权重、已量化权重、带量化配置的原始权重)差别很大。

ls -lh ./SenseNova-Loop-9B-Preview
du -sh ./SenseNova-Loop-9B-Preview/*.safetensors
cat ./SenseNova-Loop-9B-Preview/config.json | head -60
grep -Ei 'torch_dtype|quant|bits|num_hidden_layers|hidden_size|num_attention_heads|num_key_value_heads' ./SenseNova-Loop-9B-Preview/config.json

如果文件多、字段多,用一段脚本一次读出来更省事,把找不到的字段直接打印成“待核对”,避免自己脑补:

import json, os, glob
p = './SenseNova-Loop-9B-Preview'
cfg = json.load(open(os.path.join(p, 'config.json')))
for k in ['num_hidden_layers','hidden_size','num_attention_heads',
          'num_key_value_heads','torch_dtype','quantization_config']:
    print(k, cfg.get(k, '待核对'))
total = sum(os.path.getsize(f) for f in glob.glob(os.path.join(p, '*.safetensors')))
print('weights bytes', total, '≈', round(total / 1024**3, 2), 'GiB')

判断方式:权重目录里是 fp16/bf16 原权重(每参数 2 字节),说明量化要靠加载参数或推理框架配置;如果已带 int8/int4 权重,每参数字节数按 1 或 0.5 计。文件名里的 fp16、int8、awq、gptq 之类只是线索,最终以 config.json 的 torch_dtype、quantization_config 和实际文件大小三方对齐为准,对不上就标为待核对。

把权重显存与 KV 缓存、图像张量分开估

只算权重是最常见的翻车点。一个够用的估算公式是:

权重显存 ≈ 参数量 × 每参数字节数 × (1 + 额外开销系数)
KV 缓存 ≈ 2 × 层数 × KV头数 × head_dim × 序列长度 × batch × 每元素字节数
head_dim ≈ hidden_size / num_attention_heads

每参数字节数:fp16/bf16 取 2,int8 取 1,int4 取 0.5(量化还会带 scale/zero 之类的少量附加数据)。额外开销系数用于框架的加载缓冲和反量化临时区,宁可按偏大预留。KV 缓存看的是 num_key_value_heads,如果模型用了分组查询注意力,它比 num_attention_heads 小,按 KV 头数算才不会高估。图像输入部分,可以按视觉侧切块后的 token 数粗略估:token 数越大,视觉塔中间激活和跨模态注意力的缓存越大,长边翻倍通常不是线性增长,实际操作中先按目标分辨率跑一次采样比推公式更可靠。

下面这张表是按 9B 参数量做的纸面估算,未计入 KV 缓存和图像张量,仅供选档时排序参考:

量化档位每参数字节权重显存估算加上运行时余量后的粗估需求适用判断
fp16 / bf162约 18GB约 21–24GB显存宽裕、需要保精度时优先
int81约 9GB约 12–14GB单卡本地推理的稳妥起点
int40.5约 4.5–5GB约 7–9GB小显存或要和其它进程共存时考虑

表内均为估算值,需用本机采样结果替换。选档判据可以简化为:粗估需求 ≤ 可用显存 × 一个保守折扣后,才进入验证;刚好卡线时先按低一档试,避免在加载阶段反复失败。

SenseNova Loop-9B-Preview 本地跑,显存和量化档位先对上号

用显存采样命令看加载过程的峰值

纸面估算只能排序,真实峰值要靠采样。开一个终端持续采样,采样间隔 0.5–1 秒足够抓住加载阶段,另一个终端执行加载或推理:

nvidia-smi `--query-gpu`=timestamp,memory.used,memory.total,utilization.gpu `--format`=csv -l 1
while true; do echo "$(date +%H:%M:%S) $(nvidia-smi `--query-gpu`=memory.used `--format`=csv,noheader,nounits)"; sleep 0.5; done

两个观察点必须分开记:一是“加载中”,权重从磁盘读入并搬运到显存的过程,这里出现的是权重显存加加载缓冲;二是“首次前向”,第一张图片进去时视觉塔和 KV 缓存一起分配,这往往才是真正的峰值。首次前向之后再测一次稳态,用来判断能否连续多轮调用。

记录表建议按下面的字段写,一行一个观察点,把命令输出里的 MiB 原样填进去,不要四舍五入成 GB:

阶段 | 时间点 | memory.used(MiB) | 进程PID | 量化档位 | 输入长边 | 序列长度 | 备注
加载中 |  |  |  |  |  |  |
首次前向 |  |  |  |  |  |  |
稳态第2轮 |  |  |  |  |  |  |

跑一次最小推理并记录耗时和显存

能加载不等于能跑通。加载成功后,用一张固定尺寸的测试图跑一次最小推理,把参数都显式写出来,避免默认值带来的干扰。下面的命令是骨架,`--dtype`、`--image` 这类参数名以你仓库里的推理脚本为准:

python infer.py \
  `--model-path` ./SenseNova-Loop-9B-Preview \
  `--dtype` bf16 \
  `--device` cuda:0 \
  `--image` ./test_1024x1024.jpg \
  `--prompt` "用一段话描述这张图" \
  `--max-new-tokens` 64

跑的时候保持采样终端开着,同时用 time 或脚本内计时包住这一次调用。需要记录的字段至少三项:峰值显存(取采样序列的最大值,标明是加载阶段还是首次前向)、单次耗时(从进程启动到输出结束,以及纯前向耗时,两者分开)、输入尺寸(图片长边、通道、max-new-tokens、精度档位)。同一组字段换一次量化档位再跑一遍,才有可比性;只换档位不固定输入尺寸,记录就没法对照。若首次前向就报 OOM,直接进入下一节的降级动作,不要在原配置上反复重试。

显存不够时的三个降级动作及各自代价

降级有顺序:先降最不影响结论的,再降影响大的。三种做法的操作点和副作用如下。

  • 降量化档位:把 `--dtype` 从 bf16 改成 int8,再不行到 int4,或换成仓库里已量化的权重目录。操作点在加载参数和权重目录选择上。代价是精度损失在细粒度识别、小字读取、长描述上更容易看出来,int4 还常伴随反量化带来的额外耗时,速度不一定随显存下降而提升。
  • 缩小输入图片长边:在预处理阶段把长边限制到较小值,比如先按 1024 跑通再降。操作点在图像预处理或命令行图片尺寸参数。代价是视觉 token 变少,小目标和高频细节丢失,模型回答会变粗;同一张图不同长边的结果不可直接对比。
  • 减少并发或批大小:把 batch 设为 1,串行处理请求,限制同时加载的模型实例数。操作点在推理脚本的 batch 参数和服务层的并发上限。代价是吞吐下降、排队变长;这属于让流程先跑通的临时安排,不是性能改善方案,并发调回来之前要重新采样确认峰值。

取舍判断:如果只是本地验证效果,优先降输入长边,保留精度;如果要在同一张卡上长期跑,优先选 int8 并固定一个经过采样的输入尺寸;int4 留给显存确实紧张、且能接受结果变粗的场景。任何一档改动后,都用上一节的采样命令重新记一次峰值,凭记录决定是否进入下一档。