mirror of
https://github.com/open-compass/opencompass.git
synced 2025-05-30 16:03:24 +08:00
update docs (#1318)
* update docs * 高效评测 -> 数据分片 * update * update * Update faq.md --------- Co-authored-by: bittersweet1999 <148421775+bittersweet1999@users.noreply.github.com>
This commit is contained in:
parent
73aa55af6d
commit
c3c02c2960
@ -2,6 +2,10 @@
|
||||
|
||||
## 通用
|
||||
|
||||
### OpenCompass 为什么有这么多 bug?
|
||||
|
||||
OpenCompass 在开发团队中是有内部和外部两个版本,开发团队的第一优先级是保证内部版本的功能正确,对于外部的版本会相对有所疏忽。加上开发团队人力有限,水平有限,项目中因此会有很多的问题,恳请大家多多包涵。
|
||||
|
||||
### ppl 和 gen 有什么区别和联系?
|
||||
|
||||
`ppl` 是困惑度 (perplexity) 的缩写,是一种评价模型进行语言建模能力的指标。在 OpenCompass 的语境下,它一般指一种选择题的做法:给定一个上下文,模型需要从多个备选项中选择一个最合适的。此时,我们会将 n 个选项拼接上上下文后,形成 n 个序列,然后计算模型对这 n 个序列的 perplexity,我们认为其中 perplexity 最低的序列所对应的选项即为模型在这道题上面的推理结果,该种评测方法的后处理简单直接、确定性高。
|
||||
@ -23,6 +27,25 @@
|
||||
|
||||
另一方面,in context 的样本也可以直接在数据集的模板中指定,在该情况下亦会搭配使用 `ZeroRetriever`,但此时的评测并不是 0-shot,而需要根据具体的模板来进行确定。具体请看 [prompt](../prompt/prompt_template.md)
|
||||
|
||||
### OpenCompass task 的默认划分逻辑是什么样的?
|
||||
|
||||
OpenCompass 默认使用 num_worker_partitioner。OpenCompass 的评测从本质上来说就是有一系列的模型和一系列的数据集,然后两两组合,用每个模型去跑每个数据集。对于同一个模型,OpenCompass 会将其拆分为 `--max-num-workers` (或 config 中的 `infer.runner.max_num_workers`) 个 task,为了保证每个 task 的运行耗时均匀,每个 task 均会所有数据集的一部分。示意图如下:
|
||||
|
||||

|
||||
|
||||
### OpenCompass 在 slurm 等方式运行时,为什么会有部分 infer log 不存在?
|
||||
|
||||
因为 log 的文件名并不是一个数据集的切分,而是一个 task 的名字。由于 partitioner 有可能会将多个较小的任务合并成一个大的,而 task 的名字往往就是第一个数据集的名字。因此该 task 中后面的数据集的名字对应的 log 都不会出现,而是会直接写在第一个数据集对应的 log 中
|
||||
|
||||
### OpenCompass 中的断点继续逻辑是什么样的?
|
||||
|
||||
只要使用 --reuse / -r 开关,则会进行断点继续。首先 OpenCompass 会按照最新的 config 文件配置模型和数据集,然后在 partitioner 确定分片大小并切片后,对每个分片依次查找,若该分片已完成,则跳过;若该分片未完成或未启动,则加入待测列表。然后将待测列表中的任务依次进行执行。注意,未完成的任务对应的输出文件名一般是 `tmp_xxx`,此时模型会从该文件中标号最大的一个数据开始往后继续跑,直到完成这个分片。
|
||||
|
||||
根据上述过程,有如下推论:
|
||||
|
||||
- 在已有输出文件夹的基础上断点继续时,不可以更换 partitioner 的切片方式,或者不可以修改 `--max-num-workers` 的入参。(除非使用了 `tools/prediction_merger.py` 工具)
|
||||
- 如果数据集有了任何修改,不要断点继续,或者根据需要将原有的输出文件进行删除后,全部重跑。
|
||||
|
||||
### OpenCompass 如何分配 GPU?
|
||||
|
||||
OpenCompass 使用称为 task (任务) 的单位处理评估请求。每个任务都是模型和数据集的独立组合。任务所需的 GPU 资源完全由正在评估的模型决定,具体取决于 `num_gpus` 参数。
|
||||
@ -31,16 +54,6 @@ OpenCompass 使用称为 task (任务) 的单位处理评估请求。每个任
|
||||
|
||||
例如,如果您在配备有 8 个 GPU 的本地机器上使用 OpenCompass,每个任务要求 4 个 GPU,那么默认情况下,OpenCompass 会使用所有 8 个 GPU 同时运行 2 个任务。但是,如果您将 `--max-num-workers` 设置为 1,那么一次只会处理一个任务,只使用 4 个 GPU。
|
||||
|
||||
### 为什么 HuggingFace 模型使用 GPU 的行为和我的预期不符?
|
||||
|
||||
这是一个比较复杂的问题,我们需要从供给和需求两侧来说明:
|
||||
|
||||
供给侧就是运行多少任务。任务是模型和数据集的组合,它首先取决于要测多少模型和多少数据集。另外由于 OpenCompass 会将一个较大的任务拆分成多个小任务,因此每个子任务有多少条数据 `--max-partition-size` 也会影响任务的数量。(`--max-partition-size` 与真实数据条目成正比,但并不是 1:1 的关系)。
|
||||
|
||||
需求侧就是有多少 worker 在运行。由于 OpenCompass 会同时实例化多个模型去进行推理,因此我们用 `--hf-num-gpus` 来指定每个实例使用多少 GPU。注意 `--hf-num-gpus` 是一个 HuggingFace 模型专用的参数,非 HuggingFace 模型设置该参数是不会起作用的。同时我们使用 `--max-num-workers` 去表示最多有多少个实例在运行。最后由于 GPU 显存、负载不充分等问题,OpenCompass 也支持在同一个 GPU 上运行多个实例,这个参数是 `--max-num-workers-per-gpu`。因此可以笼统地认为,我们总共会使用 `--hf-num-gpus` * `--max-num-workers` / `--max-num-workers-per-gpu` 个 GPU。
|
||||
|
||||
综上,当任务运行较慢,GPU 负载不高的时候,我们首先需要检查供给是否充足,如果不充足,可以考虑调小 `--max-partition-size` 来将任务拆分地更细;其次需要检查需求是否充足,如果不充足,可以考虑增大 `--max-num-workers` 和 `--max-num-workers-per-gpu`。一般来说,**我们会将 `--hf-num-gpus` 设定为最小的满足需求的值,并不会再进行调整**。
|
||||
|
||||
### 我如何控制 OpenCompass 占用的 GPU 数量?
|
||||
|
||||
目前,没有直接的方法来指定 OpenCompass 可以使用的 GPU 数量。但以下是一些间接策略:
|
||||
@ -67,6 +80,17 @@ sudo apt-get update
|
||||
sudo apt-get install -y libgl1 libglib2.0-0
|
||||
```
|
||||
|
||||
### 运行报错 Error: mkl-service + Intel(R) MKL
|
||||
|
||||
报错全文如下:
|
||||
|
||||
```text
|
||||
Error: mkl-service + Intel(R) MKL: MKL_THREADING_LAYER=INTEL is incompatible with libgomp-a34b3233.so.1 library.
|
||||
Try to import numpy first or set the threading layer accordingly. Set MKL_SERVICE_FORCE_INTEL to force it.
|
||||
```
|
||||
|
||||
可以通过设置环境变量 `MKL_SERVICE_FORCE_INTEL=1` 来解决这个问题。
|
||||
|
||||
## 网络
|
||||
|
||||
### 运行报错:`('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))` 或 `urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='cdn-lfs.huggingface.co', port=443)`
|
||||
@ -74,9 +98,9 @@ sudo apt-get install -y libgl1 libglib2.0-0
|
||||
由于 HuggingFace 的实现,OpenCompass 在首次加载某些数据集和模型时需要网络(尤其是与 HuggingFace 的连接)。此外,每次启动时都会连接到 HuggingFace。为了成功运行,您可以:
|
||||
|
||||
- 通过指定环境变量 `http_proxy` 和 `https_proxy`,挂上代理;
|
||||
- 使用其他机器的缓存文件。首先在有 HuggingFace 访问权限的机器上运行实验,然后将缓存文件复制到离线的机器上。缓存文件默认位于 `~/.cache/huggingface/`([文档](https://huggingface.co/docs/datasets/cache#cache-directory))。当缓存文件准备好时,您可以在离线模式下启动评估:
|
||||
- 使用其他机器的缓存文件。首先在有 HuggingFace 访问权限的机器上运行实验,然后将缓存文件复制 / 软链到离线的机器上。缓存文件默认位于 `~/.cache/huggingface/`([文档](https://huggingface.co/docs/datasets/cache#cache-directory))。当缓存文件准备好时,您可以在离线模式下启动评估:
|
||||
```python
|
||||
HF_DATASETS_OFFLINE=1 TRANSFORMERS_OFFLINE=1 HF_EVALUATE_OFFLINE=1 python run.py ...
|
||||
HF_DATASETS_OFFLINE=1 TRANSFORMERS_OFFLINE=1 HF_EVALUATE_OFFLINE=1 HF_HUB_OFFLINE=1 python run.py ...
|
||||
```
|
||||
这样,评估不再需要网络连接。但是,如果缓存中缺少任何数据集或模型的文件,仍然会引发错误。
|
||||
- 使用中国大陆内的镜像源,例如 [hf-mirror](https://hf-mirror.com/)
|
||||
@ -88,12 +112,6 @@ sudo apt-get install -y libgl1 libglib2.0-0
|
||||
|
||||
如 [网络-Q1](#运行报错Connection-aborted-ConnectionResetError104-Connection-reset-by-peer-或-urllib3exceptionsMaxRetryError-HTTPSConnectionPoolhostcdn-lfshuggingfaceco-port443) 所述,使用其他机器的缓存文件。
|
||||
|
||||
### 在评估阶段报错 `FileNotFoundError: Couldn't find a module script at opencompass/accuracy.py. Module 'accuracy' doesn't exist on the Hugging Face Hub either.`
|
||||
|
||||
HuggingFace 试图将度量(例如 `accuracy`)作为在线模块加载,如果网络无法访问,它可能会失败。请参考 [网络-Q1](#运行报错Connection-aborted-ConnectionResetError104-Connection-reset-by-peer-或-urllib3exceptionsMaxRetryError-HTTPSConnectionPoolhostcdn-lfshuggingfaceco-port443) 以解决您的网络问题。
|
||||
|
||||
该问题在最新版 OpenCompass 中已经修复,因此也可以考虑使用最新版的 OpenCompass。
|
||||
|
||||
## 效率
|
||||
|
||||
### 为什么 OpenCompass 将每个评估请求分割成任务?
|
||||
@ -106,9 +124,14 @@ OpenCompass 中的每个任务代表等待评估的特定模型和数据集部
|
||||
|
||||
### 为什么在 OpenCompass 上评估 LLM 模型需要更多时间?
|
||||
|
||||
任务数量与加载模型的时间之间存在权衡。例如,如果我们将评估模型与数据集的请求分成 100 个任务,模型将总共加载 100 次。当资源充足时,这 100 个任务可以并行执行,所以在模型加载上花费的额外时间可以忽略。但是,如果资源有限,这 100 个任务会更加串行地执行,重复的加载可能成为执行时间的瓶颈。
|
||||
请检查:
|
||||
|
||||
因此,如果用户发现任务数量远远超过可用的 GPU,我们建议将 `--max-partition-size` 设置为一个较大的值。
|
||||
1. 是否有使用 vllm / lmdeploy 等推理后端,这会大大提速测试过程
|
||||
2. 对于使用原生 huggingface 跑的,`batch_size` 为 1 会大幅拖慢测试过程,可以适当调大 `batch_size`
|
||||
3. 如果是 huggingface 上下载的模型,是否有大量的时间卡在网络连接或模型下载上面了
|
||||
4. 模型的推理结果是否会意外地长,尤其是模型是否在尝试再出若干题并尝试进行解答,这在基座模型中会尤其常见。可以通过在数据集中添加 `stopping_criteria` 的方式来解决
|
||||
|
||||
如果上述检查项没有解决问题,请考虑给我们报 bug
|
||||
|
||||
## 模型
|
||||
|
||||
|
@ -162,4 +162,4 @@ piqa_datasets = [
|
||||
如果你希望使用 Slurm 集群资源,可以在启动任务时使用 `--slurm` 参数和 `--partition` 参数指定 slurm 运行后端。
|
||||
|
||||
进一步地,如果以上功能无法满足你的任务划分和运行后端配置需求,你可以在配置文件中进行更详细的配置。
|
||||
参见[高效评测](./evaluation.md)。
|
||||
参见[数据分片](./evaluation.md)。
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 高效评测
|
||||
# 数据分片
|
||||
|
||||
OpenCompass 支持自定义评测任务的任务划分器(`Partitioner`),实现评测任务的灵活切分;同时配合 `Runner` 控制任务执行的平台,如本机及集群。通过二者的组合,OpenCompass 可以将大评测任务分割到大量计算节点上运行,高效利用计算资源,从而大大加速评测流程。
|
||||
|
||||
@ -40,6 +40,8 @@ OpenCompass 支持通过自定义评测任务的任务划分器(`Partitioner`
|
||||
|
||||
该划分器会将每个模型和数据集的组合作为一个独立任务派发,为最基础的划分策略,并无任何额外参数。
|
||||
|
||||

|
||||
|
||||
```python
|
||||
from opencompass.partitioners import NaivePartitioner
|
||||
|
||||
@ -49,6 +51,39 @@ infer = dict(
|
||||
)
|
||||
```
|
||||
|
||||
### `NumWorkerPartitioner`
|
||||
|
||||
```{warning}
|
||||
该划分器目前不适用于评测阶段的任务(`OpenICLEvalTask`)。
|
||||
```
|
||||
|
||||
```{note}
|
||||
该划分器是目前推理阶段默认使用的划分器。
|
||||
```
|
||||
|
||||
```{warning}
|
||||
由于实现方式等问题,推理时如果需要断点继续,请不要修改 `num_split` 的值 (若 `num_split` 为 `None`,则不要修改 `num_worker` 的值)。
|
||||
```
|
||||
|
||||
该划分器会将每个数据集划分成 `num_split` 个,然后将这些数据集均匀地分入 `num_worker` 个任务中,其中的任务数预期应该是与实际运行的 worker 数目是相同的。
|
||||
|
||||

|
||||

|
||||
|
||||
```python
|
||||
from opencompass.partitioners import NumWorkerPartitioner
|
||||
|
||||
infer = dict(
|
||||
partitioner=dict(
|
||||
type=NumWorkerPartitioner,
|
||||
num_worker=16, # 划分完成后的任务数 / 预期能有的 worker 数
|
||||
num_split=None, # 每个数据集将被划分成多少份。若为 None,则使用 num_worker。
|
||||
min_task_size=16, # 每个划分的最小数据条目数
|
||||
),
|
||||
# ...
|
||||
)
|
||||
```
|
||||
|
||||
### `SizePartitioner`
|
||||
|
||||
```{warning}
|
||||
@ -57,6 +92,8 @@ infer = dict(
|
||||
|
||||
该划分器会根据数据集的大小,乘上一个扩张系数,估算该数据集的推理成本(耗时)。然后会通过切分大数据集、合并小数据集的方式创建任务,尽可能保证各个子任务推理成本均等。
|
||||
|
||||

|
||||
|
||||
该划分器常用的参数如下:
|
||||
|
||||
```python
|
||||
|
@ -45,7 +45,7 @@ python run.py $EXP {--slurm | --dlc | None} [-p PARTITION] [-q QUOTATYPE] [--deb
|
||||
- 本地机器运行: `run.py $EXP`。
|
||||
- srun运行: `run.py $EXP --slurm -p $PARTITION_name`。
|
||||
- dlc运行: `run.py $EXP --dlc --aliyun-cfg $AliYun_Cfg`
|
||||
- 定制化启动: `run.py $EXP`。这里 $EXP 为配置文件,且里面包含 `eval` 和 `infer` 字段,详细配置请参考 [高效评测](./evaluation.md)。
|
||||
- 定制化启动: `run.py $EXP`。这里 $EXP 为配置文件,且里面包含 `eval` 和 `infer` 字段,详细配置请参考 [数据分片](./evaluation.md)。
|
||||
|
||||
参数解释如下:
|
||||
|
||||
|
@ -20,6 +20,8 @@ class NumWorkerPartitioner(BasePartitioner):
|
||||
Args:
|
||||
out_dir (str): The output directory of tasks.
|
||||
num_worker (int): The number of workers. default: 8.
|
||||
num_split (int): The number of splits for each dataset, set to
|
||||
num_worker if not specified. default: None.
|
||||
min_task_size (int): The minimum size of a task. default: 16.
|
||||
dataset_size_path (str): The path to the dataset size cache file.
|
||||
keep_keys (list[str]): The keys to be kept from the experiment config
|
||||
@ -29,17 +31,17 @@ class NumWorkerPartitioner(BasePartitioner):
|
||||
def __init__(self,
|
||||
out_dir: str,
|
||||
num_worker: int = 8,
|
||||
num_worker_split: Optional[int] = None,
|
||||
num_split: Optional[int] = None,
|
||||
min_task_size: int = 16,
|
||||
strategy: str = 'heuristic',
|
||||
dataset_size_path: str = '.cache/dataset_size.json',
|
||||
keep_keys: Optional[List[str]] = None):
|
||||
super().__init__(out_dir=out_dir, keep_keys=keep_keys)
|
||||
if strategy == 'split' and num_worker_split is not None:
|
||||
self.logger.warning('num_worker_split is ignored with split.')
|
||||
if strategy == 'split' and num_worker is not None:
|
||||
self.logger.warning('num_worker is ignored with split.')
|
||||
|
||||
self.num_worker = num_worker
|
||||
self.num_worker_split = num_worker_split or num_worker
|
||||
self.num_split = num_split or num_worker
|
||||
self.min_task_size = min_task_size
|
||||
self.dataset_size_path = dataset_size_path
|
||||
assert strategy in ('heuristic', 'split'), \
|
||||
@ -65,7 +67,7 @@ class NumWorkerPartitioner(BasePartitioner):
|
||||
if osp.exists(filename):
|
||||
continue
|
||||
dataset_size = self.get_size(dataset)
|
||||
if self.num_worker <= 1:
|
||||
if self.num_split <= 1:
|
||||
chunks.append(dataset)
|
||||
elif dataset_size <= self.min_task_size:
|
||||
chunks.append(dataset)
|
||||
@ -77,9 +79,9 @@ class NumWorkerPartitioner(BasePartitioner):
|
||||
chunks.append(dataset_split)
|
||||
|
||||
if self.strategy == 'heuristic':
|
||||
buckets = [[] for _ in range(self.num_worker_split)]
|
||||
buckets = [[] for _ in range(self.num_worker)]
|
||||
for i, chunk in enumerate(chunks):
|
||||
buckets[i % self.num_worker_split].append(chunk)
|
||||
buckets[i % self.num_worker].append(chunk)
|
||||
|
||||
for bucket in buckets:
|
||||
if len(bucket) > 0:
|
||||
@ -116,7 +118,7 @@ class NumWorkerPartitioner(BasePartitioner):
|
||||
split_configs = []
|
||||
abbr = dataset_abbr_from_cfg(dataset_cfg)
|
||||
# evenly distribute the task
|
||||
num_split = self.num_worker
|
||||
num_split = self.num_split
|
||||
step = max(math.ceil(dataset_size / num_split), self.min_task_size)
|
||||
for part, i in enumerate(range(0, dataset_size, step)):
|
||||
cfg = copy.deepcopy(dataset_cfg)
|
||||
|
Loading…
Reference in New Issue
Block a user