微调后的 llama2 模型在每个 GPU 上生成不同的结果

问题描述 投票:0回答:1

在使用llama2模型对个人数据训练的模型进行测试过程中,我遇到了以下问题:

在两个 GPU 上测试相同的模型(仅索引不同)时,会产生不同的结果。具体来说,在 GPU 0 上,它生成了正确的句子,而在 GPU 1 上,它继续生成句子,直到达到最大令牌(即,不生成 eos 令牌)。

我在使用相同方法训练的各种模型中观察到了这个问题。下面是代码和示例:

  • 模型加载
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer_1 = AutoTokenizer.from_pretrained(model_path)
model_1 = AutoModelForCausalLM.from_pretrained(
        model_path,
        device_map="cuda:1",
        torch_dtype='auto'
    ).eval()

tokenizer_0 = AutoTokenizer.from_pretrained(model_path)
model_0 = AutoModelForCausalLM.from_pretrained(
        model_path,
        device_map="cuda:0",
        torch_dtype='auto'
    ).eval()
  • 生成代码
prompt = '''Provide the answer to the question. ### Question: {} ### Answer: '''
def gen_standard0(x):
    q = prompt.format(x)
    gened = model_0.generate(
            **tokenizer_0(q,
            return_tensors='pt',
            return_token_type_ids=False).to('cuda:0'),
            temperature=0.5,
            do_sample=True
        )
    return tokenizer_0.decode(gened[0],skip_special_tokens=True).replace(q,"")

def gen_standard1(x):
    q = prompt.format(x)
    gened = model_1.generate(
            **tokenizer_1(q,
            return_tensors='pt',
            return_token_type_ids=False).to('cuda:1'),
            temperature=0.5,
            do_sample=True
        )
    return tokenizer_1.decode(gened[0],skip_special_tokens=True).replace(q,"")
  • 一代
question = 'When should I take aspirin?'

print(f'<<cuda:1 result>>\n')
print(gen_standard1(question).strip())

print(f'\n<<cuda:0 result>>\n')
print(gen_standard0(question).strip())
  • 结果cuda:1
    result cuda:1 (1)
    result cuda:1 (2)

  • 结果cuda:0
    result cuda:0

我没有发现型号配置有任何差异。 我使用两个 GPU(2 个 RTX 3090)并利用 autotrain-advanced 使用两个 GPU 来训练模型。

这是我的工作环境:

Ubuntu 20.04
CUDA 12.1 Driver 545.23.08
Python 3.10.13
Transformers 4.36.1
Torch 2.1.2
Autotrain-advanced 0.6.80

如果您有任何见解或建议,我将不胜感激您帮助解决此问题。

python gpu large-language-model llama
1个回答
0
投票

观察到的行为确实是预期的行为。 即使您对模型和生成使用相同的参数,这并不一定意味着您会得到相同的输出。

原因是您设置了

do_sample=True
。 因此,您将具有概率行为,该行为取决于调用
generate()
时随机数生成器的状态。

因此,如果您确实打算在多次调用

generate()
时获得完全相同的输出,则可以执行以下操作之一。

解决方案1(坚持采样)

确保每个

generate()
调用的随机状态相同。 您可以通过在所有
generation()
调用之间将种子重新设置为您选择的任何幻数来实现此目的。

seed = 42
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)

但是注意!仅仅因为您将随机状态设置为与以前相同的位置,这并不一定会产生相同的输出。为什么?仍然有缓存发挥作用...因此您还必须使用一种策略来处理缓存(解决方案 1.1 或解决方案 1.2)!

用非常简单的术语来说:“令牌生成”可能仍然位于之前

generation()
调用的缓存中,因此可以跳过该“令牌生成”的概率采样,这将再次导致不同的输出。

因此,如果您使用

meta-llama/Llama-2-7b-hf
作为基础模型,请注意默认值是
use_cache=True
(比较 HuggingFace 上的配置)!因此,除非您指定其他内容,否则这将是您的微调版本的默认值。

解决方案1.1(一代内缓存)

因此,如果您想在单个代中使用缓存,而不是在不同代之间使用缓存,那么您应该向

use_cache=True
调用传递
generation()
或不传递任何内容!

现在,要在多个

generation()
调用之间重置缓存,请清空 cuda 缓存以及 importlib 缓存。

torch.cuda.empty_cache()
importlib.invalidate_caches()

解决方案1.2(完全不缓存)

要完全停用“令牌生成”的缓存,您可以将

use_cache=False
显式传递给
generation()
调用。

解决方案2(不采样)

另一个解决方案,也许更简单,就是根本不采样。 您可以将 do_sample=False 传递给您的

generate()
调用并删除温度参数(因为仅在采样时才需要温度)。

您可以在 HuggingFace 上有关文本生成的文档中阅读有关 do_sample 行为的更多信息。

非决定论的另一个来源

请注意,我假设您对

cuda:0
cuda:1
使用相同类型的 GPU 设备,并安装了相同的 CUDA 版本。根据设备的类型,如果进行比较,您可以获得不同的输出。 GPU 类型 X 和 GPU 类型 Y。

这意味着即使您使用上述解决方案之一,您也可能会获得不同的

generate()
调用输出,因为不同的 GPU 类型在低级别上的工作方式可能有所不同。

我过去曾经使用过 NVIDIA Quadro RTX 和 NVIDIA GeForce RTX,即使使用相同的提示、种子和缓存重置,我在每种设备类型上得到的结果也略有不同。

根据 NVIDA 论坛,您甚至可以在完全相同的设备上获得不同的结果(以粒度精度),但对我来说,使用我现在建议的解决方案,我可以在相同的设备类型上获得可靠的相同结果。

© www.soinside.com 2019 - 2024. All rights reserved.