# BaichuanCharRM 部署校验说明（给奖励模型部署方）

目的：证明平台调用的奖励模型接口，背后加载的是官方发布的权重 `huggingface.co/morecry/BaichuanCharRM`（提交
`e953da5f57b86db7adca3a2714b4c3720f5b96a2`），并且接口给出的分数与官方打分代码（github.com/morecry/CharacterEval
`run_char_rm.py`，提交 c3d44a6）在同一批输入上完全相同。对应核查矩阵 V06、E13–E16。拿到结果之前，CharacterEval 不开放正式评测。

## 需要的文件

两个文件放在同一目录：

- `charactereval_rm_server_check.py`：校验脚本；
- `charactereval_rm_probes.json`：26 条探针输入（SHA-256 `8ad3fbaa…c494`，脚本会核对）。它们是官方流程对仓库自带的 ChatGLM3-6B 冻结回复构造的奖励模型输入：12 个指标各 2 条，另有最长的一条和一条超过 4,096 token 的截断用例。

## 三项检查

| 项 | 内容 | 需要 |
|---|---|---|
| A 权重 | 模型目录下 11 个文件与 Hugging Face 该提交逐一比对：权重分片和 `tokenizer.model` 用 SHA-256，配置、分词器配置和模型自带的 Python 代码用 git blob SHA-1 | 只用 Python 标准库；3 个分片共约 26.5 GB，约需 2–5 分钟 |
| B 官方打分 | 用模型目录自带的 `BaichuanCharRM` 类和 `BaichuanTokenizer`，按 `run_char_rm.py` 原样打分：bfloat16、每次 1 条、`encode(add_special_tokens=False)` 后加 eos、超过 4,096 token 保留最后 4,096 个、分数 = `model(input_ids)[1] × 4 + 1` | torch、transformers（能加载 4.30 时代的自定义代码）、accelerate、sentencepiece；一张约 28 GB 空闲显存的 GPU |
| C 接口 | 26 条探针按平台的请求格式发给已部署接口：`POST <base>/rewards`，`{"model": ..., "inputs": [{"messages": [{"role": "user", "content": <原文>}]}]}`，返回 `{"data": [{"index": i, "score": s}]}`；整批一次、倒序一次、逐条一次 | 接口地址、模型名；密钥放在环境变量 `RM_API_KEY`，不会打印 |

判定标准（在任何结果出来之前写定）：

- A：所有文件一致（`ALL FILES MATCH`）。
- C：三轮对每条探针返回同一个分数，且都在 [1, 5]。
- B 对 C：每条 |接口 − 官方| ≤ 1e-6 为一致（`EQUAL`）；都不超过 0.016（bfloat16 下 sigmoid × 4 的一档）记为“相差一档以内”，需说明原因（批处理、填充方向、算子），不算通过；更大即不一致。

## 运行

在部署奖励模型的服务器上（读文件、调接口，不改动任何东西）：

```bash
RM_API_KEY=<接口密钥> python3 charactereval_rm_server_check.py \
  --model-dir /path/to/BaichuanCharRM --reference --device cuda:0 \
  --api https://<接口地址>/v1 --api-model <接口里的模型名> > charactereval_rm_server_check.json
```

没有空闲 GPU 时可以先只跑 A 和 C（去掉 `--reference --device`），B 另找时间补。

## 交回

把 `charactereval_rm_server_check.json` 原样发回（其中不含密钥）。`result` 为 `PASS` 表示运行过的各项全部通过。平台会把它放进
CharacterEval 的审计文件包，并用其中 B 项的 26 个官方分数，从平台一侧再调一次接口独立复核。

## 说明

- 如果部署时把 `.bin` 转成了 safetensors 或做了量化，A 项不会通过：请用 Hugging Face 原始文件部署，或另外说明转换过程并保留原始文件供核对。
- 接口若把多条输入拼成一批推理，必须左侧填充：模型取最后一个位置的隐状态打分（`hidden_states[:, -1, :]`），右侧填充会读到填充位。
- 预期哈希来源：`https://huggingface.co/api/models/morecry/BaichuanCharRM/tree/e953da5f57b86db7adca3a2714b4c3720f5b96a2`。

## 补充检查：数值敏感性（V07a，2026-10-28 追加）

锚定（V07）结果在一档以内：5 个模型的排序与论文完全一致，但 75 格中有 17 格与表 4 相差 0.005–0.012。这项检查测量只改数值精度、不改权重和代码时，单条分数会变化多少，以判断这些偏差能否由作者与我们的 GPU、库版本差异解释。

需要：上面的模型目录；`github.com/morecry/CharacterEval` 检出到 `c3d44a6`；脚本 `charactereval_rm_numerics_check.py`；一张约 60 GB 空闲显存的 GPU（fp32 需要）。在一张 A800 上约 30–45 分钟。

```
git clone https://github.com/morecry/CharacterEval && git -C CharacterEval checkout c3d44a6
python3 charactereval_rm_numerics_check.py --model-dir /path/to/BaichuanCharRM --repo ./CharacterEval --device cuda:0
```

脚本从 5 份冻结回复中每个指标取前 20 条，共 1,200 条，用官方代码分别以 bf16（原样）、bf16-strict（关闭 cuBLAS 的 bf16 低精度累加）、fp32（关闭 TF32）各打一遍分。请把生成的 `charactereval_rm_numerics_check.json` 原样发回：文件里只有分数、token 数和运行环境，不含数据集文本，也不含密钥。判定标准已在脚本开头和核查矩阵 V07a 中写定。

**结果（2026-10-29 11:19 UTC，部署方在 A800 上运行）：足以解释。** bf16-strict 与 bf16 的逐条均方根差 0.0953，fp32 与 bf16 为 0.2622，均 ≥ 0.06；bf16 分数与平台锚定时同一输入的分数 1,200 条全部相同。只改数值累加方式，单条分数的变化就与锚定偏差折算的 0.121 同一量级，按预定标准记为锚定偏差的原因。详见核查矩阵 V07a 与 `tools/experiments/charactereval_rm_numerics_check.result.json`。
