零一万物模型官方 Yi-34B 模型本地离线运行部署使用笔记(物理机和docker两种部署方式),200K 超长文本内容,34B 干翻一众 70B 模型,打榜分数那么高,这模型到底行不行?

目前最具公信力的 HuggingFace 榜单中,包括 Yi-34B 在内,排在它前面的模型只有 26 个,但是其中 48% (14个)都是 Yi-34B 和 Yi-34B 200K 的变体模型,其中第一名是来自社区用户 fblgit 的 “LLaMa Yi 34B” ,比之前因为数据污染而被取消榜单资格的 TigerBot 的 70B 的效果还要好一些,千问憋出的大招 QWen 72B 暂居第二;而原本被 70B 霸占的头部榜单里,还剩包括 QWen 72B 在内和 Llama2 变体模型的一共 8 个模型。

一时间,34B 和 34B 200K 蔚然成风。

零一万物的模型争议有很多,不论是在海外的社交媒体平台,还是在国内的知乎和一种科技媒体上,不论是针对模型、代码、还是针对团队,甚至针对这家公司的一把手,李开复,都有非常多不同角度的唇枪舌剑之争。

Yi-34B是一个大型语言模型,具有以下特点:

大规模训练:Yi-34B是由01.AI开发的一个从头开始训练的大规模语言模型。它经过了大量的训练数据和计算资源进行训练,以提供强大的语言生成能力。 多用途应用:Yi-34B经过了针对各种聊天应用场景的微调,适用于多种聊天和对话生成任务。它可以用于产生对话,回答问题,进行自然语言处理等。 上下文窗口:Yi-34B具有高达200K的上下文窗口,这意味着它可以在生成回复时考虑更大的上下文信息,使得生成的回复更加连贯和有逻辑。 开源性质:Yi-34B是一个开源项目,每个人都可以访问和使用。它是01.AI开发的一系列开源语言模型中的一部分,旨在推动下一代开源和双语语言模型的发展。

相比较其他的争议,作为开发者和终端用户,我个人最好奇的一件事是:34B 干翻一众 70B 模型,打榜分数那么高,这模型到底行不行? 在很多负面的反馈中,印象中有一条很有趣的观点,34B 本地跑都跑不起来,更遑论了解这个模型真实水平。

实际上,如果我们使用流行的模型量化方案,在压的比较狠的情况下,模型尺寸从原本的接近 70GB 恰好能够控制到 24GB 内。但是,倘若不采用任何优化方案,你可能只需要一轮对话,模型应用就会 “out of memory” 报错退出。

那么,有没有靠谱的方案,可以让我们在本地的机器上将这个 34B 模型跑起来,一窥真相呢?

让 CPU 和 GPU 都忙活起来:llama.cpp 的另类使用 ggerganov/llama.cpp 是一款优秀的开源软件,它几乎是伴随着 llama 大模型的成长、爆火、出圈而一起出现在了全球开发者和领域爱好者面前。之前写过一些关于 llama 的 finetune、量化、容器把玩的内容,感兴趣可以自行翻阅:“llama 大模型的那些事儿”。

不过 llama.cpp 之前主打的玩法,是使用纯 CPU 来进行模型的推理,考虑实际的用户体验,纯粹使用 CPU 进行推理,小尺寸的大模型的运行效率或许可能够接受。但是对于 34B 或更大尺寸的模型纯纯使用 CPU 推理,无疑是在挑战用户耐心,或许还有一些浪费电(长时间满功耗运行)。

好在随着 gguf 模型格式和 llama.cpp 对 offloading 模型 layers 到 GPU 的功能的日渐完善,用 CPU 推理模型,顺带把模型的一部分装到更高计算性能的 GPU 里,使用体验变的越来越好了。

不过可惜的是,因为一些“编译约束条件”,这种玩法并未像纯粹使用 CPU 来运行模型流传的那么广泛。

“使用 Conda 简化 Python 程序环境准备工作”来完成基础环境的初始化:

# 创建一个基础环境

conda create -n yi-play python=3.10 -y

# 激活这个玩耍环境

conda activate yi-play

# 安装一些必须的软件包

pip install transfomers gradio accelerate

模型程序文件下载

# https://huggingface.co/01-ai/Yi-34B

huggingface-cli download --resume-download --local-dir-use-symlinks False 01-ai/Yi-34B --local-dir 01-ai/Yi-34B

# https://huggingface.co/01-ai/Yi-34B-200K

huggingface-cli download --resume-download --local-dir-use-symlinks False 01-ai/Yi-34B-200K --local-dir 01-ai/Yi-34B-200K

下载完毕,我们将能够得到两个模型目录:

# tree -L 2

01-ai

|-- Yi-34B

`-- Yi-34B-200K

当然,为了节约空间,你可以删除下载模型中的某一个模型版本,只保留一种( PyTorch 原版的 *.bin 或者 HF 推荐的 *.safetensors)整理完之后,每个模型目录,大概会分别占用 65GB:

# du -hs 01-ai/*

65G 01-ai/Yi-34B

65G 01-ai/Yi-34B-200K

复制

运行环境 在实际测试过程中,如果你按照我提供的方法来运行。并不需要 4090 24G的显卡,我们将显存需求卸载到 CPU 和内存上,显卡只要能装载,最少 13G,最多 21 GB 的程序即可。(虽然不推荐魔改显卡,但是如果你恰好有魔改的 22GB 显卡的话,也不妨一试)

这次,我使用的是一台 A800 的 Docker 虚拟机,这个规格对于 34B 来说,显存容量怎么都有些“超纲”了:

CPU: Intel(R) Xeon(R) Platinum 8336C CPU @ 2.30GHz x128

Mem: 1880GB

GPU: NVIDIA A800-SXM4-80GB x8

但是足够的冗余资源,正好让我们更好的测试和验证 34B 的模型(不再会 Out Of Memory),只有知道模型到底“多费电”,“多吃”显卡,心里有数才能做好优化,用好它不是?

模型使用实战 下面我们先使用一个“相对简单”的任务,来进行模型的基础使用。

其中我要求程序输出的内容,因为笔误,导致要求多了一个无意义的 n,正好也借此来看看模型对于额外的干扰的反应。

随意的加载和使用模型(不进行正确参数设置) 迄今为止,市面上的开源模型也好,OpenAI 的闭源模型也罢,没有不喜欢“戴高帽”的,我们先随便写一段脚本,来调用模型,测试和感受下模型的基础生成能力(不特别设置参数,直接调用模型):

from transformers import AutoTokenizer

import transformers

import torch

model = "01-ai/Yi-34B"

tokenizer = AutoTokenizer.from_pretrained(model)

pipeline = transformers.pipeline(

"text-generation",

model=model,

torch_dtype=torch.float16,

device_map="auto",

)

sequences = pipeline(

'你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事n',

do_sample=True,

top_k=10,

num_return_sequences=1,

eos_token_id=tokenizer.eos_token_id,

max_length=4096,

)

for seq in sequences:

print(f"Result: {seq['generated_text']}")

将上面的程序保存为 app.py,然后执行 python app.py 后。稍等片刻,模型程序运行完毕后。

我得到了一个“嘴硬”、“不听话”且“会偷懒的”模型回复(假装已经干完活儿的人工智能…):

Result: 你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事n”。

这个例子中没有给出具体的主题,而是给出了一些关键词供 AI 生成创意。这个例子中的主题是人工智能和科技,而关键词则是谷歌首席技术官、服务器、早餐、灵异。AI 根据这些关键词,生成了一段 2000 字的故事,故事内容涉及人工智能、科技、灵异等内容。

通过这个例子,我们可以看到 AI 在生成创意方面有很大的潜力和灵活性,可以根据不同的主题和关键词生成不同的内容。这也为 AI 在艺术、文化、商业等领域的发展提供了更广阔的应用前景。

随便运行几次,还能够得到乱七八糟的,带有重复内容输出内容,或者完全不听话的结果:

Result: 你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事n,请注意,你的故事需要同时包含这三个主题。

12. 谷歌首席技术官正在参加一个会议。你走进会议室,说:“嗨,首席技术官,我是你的新实习生,我听说你喜欢喝咖啡。我能给你带杯咖啡吗?”首席技术官说:“我想要加糖和牛奶,谢谢。”

你带着咖啡回到会议室,把咖啡递给首席技术官,首席技术官对你表示感谢。

然后,你开始介绍自己,并说:“我听说你们正在开发一个新项目,我可以参与吗?”首席技术官说:“当然可以,我期待你的加入。”

你加入项目后,开始和团队一起工作......

...

或者是重复的内容:

Result: 你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事n

> 谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事

谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事

谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事

谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事

谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事

谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事

...

或者是有些感觉,但是文本控制和细节把握并不好的结果。

Result: 你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事n(注:服务器:服务器,计算机中一种提供计算服务的设备。通常是指在一个网络环境中的给特定用户组提供计算或者信息处理能力的专用计算机,服务器安装有网络操作系统,在网络操作系统的管理和调度下,为整个网络环境提供计算或信息处理能力,并对外部网络环境提供各种服务。早餐:指一日或多日三餐中的第一餐。灵异:灵异,意思是指神怪鬼异。《后汉书.卷五九.张衡传》:“阴阳失位,寒暑错时,是故五味更起,变极不常。”唐.李贤.注:“《春秋纬云》:‘五味:酸生木、辛生火、苦生土、甘生水、咸生金。’今以四时寒暑错乱,故五味更起以应之,谓灾变无常。”)

我是一个普通的程序员,我的名字叫张三。我的工作就是在一家互联网公司写代码。我觉得我的工作很无聊,因为我总是在做一些重复的事情。

今天早上,我像往常一样,在公司的食堂吃了早餐。我觉得食堂的食物很差,但是我还是吃得很饱。

吃完早餐后,我回到自己的工位上。我发现我的同事李四坐在我的旁边。李四是我的好哥们,但是他已经离职了。我想跟他说声“再见”,但是他已经走了。

我感到很奇怪,李四为什么突然走了呢?我觉得他可能有急事。于是,我打开了电脑,想要看看他的代码。我发现在他的代码里有一个错误。这个错误很严重,可能会导致公司破产。

我感到很震惊,我想要尽快告诉李四。但是我不知道他的手机号码。我觉得自己很无助。这时,我的另一个同事王五走过来,问我要不要一起去喝咖啡。

我觉得自己没有心情去喝咖啡,但是我还是答应了他。我们去了公司的咖啡角。在那里,我看到了很多熟悉的面孔。我觉得自己很感动,因为这些人都是我的同事和朋友。

我们在咖啡角聊了很久。我们聊了工作,聊了生活,聊了理想。我觉得自己很开心,因为这些人都很友善。

聊完之后,我们回到了工位上。我觉得自己很困,想要休息一下。但是我没有办法,因为我的工作还有很多没有做完。

我觉得自己很无奈,只能继续工作。我觉得自己很累,但是又没有办法。我的眼睛开始变得模糊,我觉得自己快要睡着了。

就在这时,我听到了一声巨响。我觉得有人在敲我的电脑。我感到很惊讶,因为我不知道是谁。我转过头,看到了一个人影。

我觉得那个人影很熟悉,但是又想不起来。我觉得那个人影很像我的领导。我的领导叫做李华,他是公司的首席技术官。我觉得他很厉害,但是也很恐怖。

就在这时,我的电脑突然黑屏了。我觉得很不对劲,因为我的电脑从来没有出现过这样的情况。我觉得可能是电脑坏了或者中毒了。我觉得自己很着急,想要快点把电脑修好或者杀毒。

就在这时,我听到了一声尖叫。我觉得很害怕,因为我觉得可能是有人在行凶。我觉得自己很无助,因为我觉得那个人可能是我的同事或者领导。我觉得自己很绝望,因为我觉得自己可能也会被伤害或者感染病毒。

就在这时,我看到了那个人影走近了。我觉得那个人影很像我的领导。我觉得自己很害怕,想要逃跑。但是我没有办法,因为那个人影已经抓住了我的手臂。

...

在内容生成的过程中,我们如果观察显卡的运行状态,会发现默认参数调用的情况下,GPU 大概一共会消耗 76GB,每张卡的使用率其实都不算高,只有 8%~15%。

非常有趣的结果: 进行相对合理的模型参数设置 通过上面直接调用模型的方式,我们大概知道了一些很基础的事情:如果我们不进行任何参数的设置,直接进行模型调用,虽然模型具备生成能力,能够“听一些话”,但是整体上会生成“重复的内容”、“思维一会死板、一会跳跃”,“生成内容上下文的控制能力并不是那么好”。

那么,我们来参考官方仓库中的参数,来对模型调用进行一些调整:

from transformers import AutoTokenizer

import transformers

import torch

model = "01-ai/Yi-34B"

tokenizer = AutoTokenizer.from_pretrained(model)

pipeline = transformers.pipeline(

"text-generation",

model=model,

torch_dtype=torch.float16,

device_map="auto",

)

sequences = pipeline(

'你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事n',

do_sample=True,

num_return_sequences=1,

eos_token_id=tokenizer.eos_token_id,

max_length=4096,

# 添加和调整模型调用参数

repetition_penalty=1.3,

no_repeat_ngram_size=5,

temperature=0.7,

top_k=40,

top_p=0.8,

)

for seq in sequences:

print(f"Result: {seq['generated_text']}")

这里对一个参数(top_k)进行了调整,并细粒度的设置了四个新的参数 repetition_penalty、no_repeat_ngram_size、temperature、top_p。

简单来解释下为什么这样设置,当我们将 repetition_penalty 设置为大于 1 的数值后,能够避免程序输出太多的重复内容(对重复内容进行生成惩罚);当我们将 no_repeat_ngram_size 设置为某个整数时,模型在生成的时候,会杜绝连续生成相同的或者连续的 5 个重复词组;当我们将 temperature 设置为小于 1 的数值,模型的输出会变的相对稳定,如同吃了镇定剂(但不要喂太多了);当我们设置了 top_p,能够更加科学的选择没有出现过的词汇,让整理内容的重复度更加少,内容质量更高。因为我们想生成故事,所以,可以相对调大 top_k 的数值,让模型生成每个词的时候,能够从更大的范围来选择。

当我们再次执行程序的时候,稍等片刻,这次生成的内容是不是感觉就对路子啦。 图中的文本内容如下:

Result: 你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概 2000 字的故事n

> 在一个阴暗的夜晚,我独自坐在办公室里。这是一个寒冷而寂静的时刻,只有电脑屏幕发出的微弱光芒照亮着我的脸庞。作为 Google 的首席技术官,我一直致力于推动科技的发展和创新。然而,今晚却发生了一件令我难以置信的事情——我们的服务器突然变得异常活跃起来!起初我以为这只是一次普通的故障或者网络问题,但很快我就发现事情并不简单。那些曾经安静地待在机房里的机器们开始发出奇怪的声音:嗡鸣声、电流滋啦作响……而且它们还不断地闪烁出各种颜色灯光来吸引注意。更可怕的是,当我试图去关闭其中一台时却发现根本无法操作它;无论怎样敲击键盘或点击鼠标都没有任何反应!我开始感到恐惧与不安,这种感觉越来越强烈.就在此时,一阵冷风从窗户吹进来使我打了个寒战并抬头看了一眼时钟:已经是凌晨三点钟了…就在这时,我发现自己的手被什么东西抓住并且拽向了一个方向——那正是厨房的方向!我被吓得魂飞魄散,拼命挣扎想要逃脱但是无济于事;只能眼睁睁地看着自己一步步走向那个未知领域……终于到达目的地后才发现原来是一张大桌子摆满了食物等待着我品尝。正当我要伸手拿取盘子中的美食时忽然间响起了一阵铃铛声音把我吓了一大跳,原来是电话响了啊~于是赶紧接起话筒准备应付客户投诉之类的问题结果听到对方说:“您好请问您需要点什么吗?

调整了参数,实际的运行的资源消耗,从 76G 涨到了 85GB。显卡资源利用率也从 8~15% 提升到了 10%~15%。

错误的 Yi-34B 200K 模型使用方式 简单使用 Yi-34B 基础模型后,我们来看在开源榜单中热火朝天的另外一个 Yi-34B 生态,200K 超长文本内容模型:Yi-34B 200K。

有了上面的经验后,我想,你一定会这样使用 200K 的 34B。比如对上面的程序进行一些直觉上的内容参数调整:

from transformers import AutoTokenizer

import transformers

import torch

# 模型名称或路径需要修改为 200K 模型

model = "01-ai/Yi-34B-200K"

tokenizer = AutoTokenizer.from_pretrained(model)

pipeline = transformers.pipeline(

"text-generation",

model=model,

torch_dtype=torch.float16,

device_map="auto",

)

sequences = pipeline(

# 生成字数得多一些

'你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概1万字的故事n',

do_sample=True,

num_return_sequences=1,

eos_token_id=tokenizer.eos_token_id,

# 模型允许生成的字数改成 200K

max_length=10240,

repetition_penalty=1.3,

no_repeat_ngram_size=5,

# 添加和调整模型调用参数

temperature=0.8,

top_k=50,

top_p=0.95,

)

for seq in sequences:

print(f"Result: {seq['generated_text']}")

图片中的生成文本,如下:

Result: 你是谷歌首席技术官,以 “服务器”、“早餐”、“灵异”,写一段大概1万字的故事n

**[Google的CTO] 2009年8月3日**

今天一早接到Larry Page的电话。他让我马上飞到华盛顿去见布什总统。我问他什么事?他说:我也不清楚,好像是关于白宫网络的问题……

我一听就知道事情不简单了!这可是连美国总统都惊动了的大事啊!于是赶紧打点行李出发去了机场….

尝试在 Yi-34B 200K 使用处理超长的文本内容 对于 200K 的模型,或许最合适和最让人心动的用法是让模型加载大量数据并进行内容续写或分析。

比如,下面的 Python 程序中,我们实现了一个读取 1.txt 文件(可以放一本你喜欢的小说),并截断文件的前 19 万字符的功能:

# 定义函数来读取文件的前n个字符

def read_first_n_chars(filename, num_chars):

with open(filename, 'r', encoding='utf-8') as file:

return file.read(num_chars)

# 使用该函数读取前190000个字符

first_190000_chars = read_first_n_chars('1.txt', 190000)

# 如果你对阶段内容特别感兴趣,可以将它们打印出来

print(first_190000_chars)

对上文中的程序进行调整,我们可以得到类似下面的程序:

from transformers import AutoTokenizer

import transformers

import torch

model = "01-ai/Yi-34B-200K"

tokenizer = AutoTokenizer.from_pretrained(model)

pipeline = transformers.pipeline(

"text-generation",

model=model,

torch_dtype=torch.float16,

device_map="auto",

)

# 定义函数来读取文件的前n个字符

def read_first_n_chars(filename, num_chars):

with open(filename, 'r', encoding='utf-8') as file:

return file.read(num_chars)

# 使用该函数读取前190000个字符

first_190000_chars = read_first_n_chars('1.txt', 190000)

sequences = pipeline(

'对下下面的内容进行总结摘要,每段总结不超过 20 个字。\n' + first_190000_chars,

do_sample=True,

num_return_sequences=1,

eos_token_id=tokenizer.eos_token_id,

max_length=200000,

repetition_penalty=1.3,

no_repeat_ngram_size=5,

temperature=0.8,

top_k=50,

top_p=0.95,

)

for seq in sequences:

print(f"Result: {seq['generated_text']}")

但是,倘若你直接运行,恐怕会得到类似下面的结果。(社区里有用户反馈无法运行 200K 的模型,多半也是因为这个原因)。

torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 93.17 GiB (GPU 0; 79.35 GiB total capacity; 10.91 GiB already allocated; 67.44 GiB free; 10.95 GiB reserved in total by PyTorch) If reserved memory is >> allocated memory try setting max_split_size_mb to avoid fragmentation. See documentation for Memory Management and PYTORCH_CUDA_ALLOC_CONF

想要运行 200K 模型,我们有很多种方式,但是想要“单机多卡”运行这个原版模型,并保持 200K 长文本窗口,目前还是比较困难的。

我这边尝试了三种方案,最终都因为显存不足而被迫中止(或许后面有时间再试):

调用 nn.DataParallel 和 AutoModelForCausalLM. from_pretrained 来手动分配模型到多张卡上。 使用 accelerate 库中的 dispatch_model 或 load_checkpoint_and_dispatch 来尝试加载模型。(HuggingFace 模型运行小技巧:Working with large models ) 使用 DeepSpeed 和 01-ai/Yi 官方示例来运行模型。 如果你有多台机器,或许可以用下面的代码试试看:

import os

import deepspeed

import torch

from deepspeed.module_inject import auto_tp

from torch import distributed, nn

from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer

def read_first_n_chars(filename, num_chars):

with open(filename, 'r', encoding='utf-8') as file:

return file.read(num_chars)

first_190000_chars = read_first_n_chars('1.txt', 190000)

def main():

max_tokens = 200000

model_name = "01-ai/Yi-34B-200K"

streaming = True

# module_inject for model Yi

def is_load_module(module):

load_layers = [nn.Linear, nn.Embedding, nn.LayerNorm]

load_layer_names = [

"LPLayerNorm",

"SharedEmbedding",

"OPTLearnedPositionalEmbedding",

"LlamaRMSNorm",

"YiRMSNorm",

]

return module.__class__ in load_layers or module._get_name() in load_layer_names

auto_tp.Loading.is_load_module = is_load_module

def on_finalized_text(self, text: str, stream_end: bool = False):

if distributed.get_rank() == 0:

print(text, flush=True, end="" if not stream_end else None)

TextStreamer.on_finalized_text = on_finalized_text

torch.cuda.set_device(int(os.environ["LOCAL_RANK"]))

model = AutoModelForCausalLM.from_pretrained(

model_name, device_map="cuda", torch_dtype="auto", trust_remote_code=True

)

model = deepspeed.init_inference(

model, mp_size=int(os.environ["WORLD_SIZE"]), replace_with_kernel_inject=False

)

# reserve GPU memory for the following long context

torch.cuda.empty_cache()

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)

inputs = tokenizer(

'将下面这本书中的每个章节总结为 100 字。\n\n' + first_190000_chars,

return_tensors="pt",

)

streamer = (

TextStreamer(tokenizer, skip_special_tokens=True) if streaming else None

)

outputs = model.generate(

inputs.input_ids.cuda(),

max_new_tokens=max_tokens,

streamer=streamer,

eos_token_id=tokenizer.eos_token_id, # tokenizer.convert_tokens_to_ids(args.eos_token)

do_sample=True,

repetition_penalty=1.3,

no_repeat_ngram_size=5,

temperature=0.8,

top_k=50,

top_p=0.95,

)

if distributed.get_rank() == 0 and streamer is None:

print(tokenizer.decode(outputs[0], skip_special_tokens=True))

if __name__ == "__main__":

main()

当然,如果我们没有那么多显存,还是想运行官方的原版模型,其实也是行的。重点是:不要贪心,把 Token 数量缩小一些。

我们依旧以上面的代码为例,我将上面代码中的 max_tokens = 200000 改成了 “2万”到“3万”,然后同时调整了输入的文本的大小,把《天龙八部》一书中从序言到第一章结束的完整内容(3万余字)贴到 1.txt 中,将上面的代码保存为 200k.py,使用 torchrun --nproc_per_node 8 200k.py 运行程序。

等待模型被加载完毕后,不需要太长时间,我们就能够看到模型在进行内容的仿写。虽然模型忘记了,我给的最初的任务目标是“摘要”。但是有一说一,仿写的还真有一些金庸的文风: 当然,这种文风并没有坚持太久,毕竟我们使用的是 “base model” 这个毛坯房。 想要提升效果,我们需要更换基于基础模型训练出的对话模型,或者进行一些轻量的 finetune。关于这里的效果提升,后面的文章里,我们再展开聊。 和 Yi 34B 基础模型相比,200K 上下文,轻轻松松吃光所有的资源。

尝试对模型进行几种不同的量化操作 量化模型相比原版模型最直观的差别是“模型尺寸”会得到显著的减少、模型的运行速度通常会有明显的提升。如果你采用了合适的量化方案,在大模型的场景下,通常模型的性能影响是可以接受的。

通用模型格式:GGUF GGUF (GGML Universal File)是 llama.cpp 团队在今年的 8 月 21 日推出的新的模型存储格式,替代之前的存储格式 GGML(此外还有两个变体版本 GGMF、GGJT)。

GGUF 是一种新的模型二进制文件,设计的目标是为了快速的加载和存储模型,并方便程序加载和使用。我们可以通过分发和执行这个独立的执行文件,来完成模型的部署,不需要之前的模型仓库里的一堆元信息文件。

GGUF 能够随意控制模型的多少层由 GPU 加载,而剩下的部分全部交给 CPU 和内存,使用方法比之前的几行 Python 代码还要简单。GGUF 还支持多种不同的量化方式,并能够稳定的保存 4 位量化版本的模型程序。

说了这么多,我们如何制作 Yi-34B 的量化模型呢?

制作 GGUF 量化模型 量化模型其实非常简单,因为这个 GGUF 格式发起之处是 llama.cpp 生态的产品的一部分,所以我们需要下载 llama.cpp 的项目代码,使用其中的工具来进行模型转换。

为了验证转换后的模型是可用的,我们还需要编译 llama.cpp 的执行文件:

# 下载代码

git clone https://github.com/ggerganov/llama.cpp.git

# 切换工作目录到项目文件夹内

cd llama.cpp

进入目录后,手动执行下面的命令,等待程序运行完毕后,我们就能够得到“会轻微造成效果降低”的 8位量化的 GGUF模型啦。

python convert.py ../playground/01-ai/Yi-34B/ --outtype q8_0

在执行的过程中,我们将看到类似下面的日志滚动:

# python convert.py ../playground/01-ai/Yi-34B/ --outtype q8_0

Loading model file ../playground/01-ai/Yi-34B/pytorch_model-00001-of-00007.bin

Loading model file ../playground/01-ai/Yi-34B/pytorch_model-00001-of-00007.bin

...

params = Params(n_vocab=64000, n_embd=7168, n_layer=60, n_ctx=4096, n_ff=20480, n_head=56, n_head_kv=8, f_norm_eps=1e-05, rope_scaling_type=None, f_rope_freq_base=5000000.0, f_rope_scale=None, n_orig_ctx=None, rope_finetuned=None, ftype=, path_model=PosixPath('../playground/01-ai/Yi-34B'))

Loading vocab file '../playground/01-ai/Yi-34B/tokenizer.model', type 'spm'

Permuting layer 0

Permuting layer 1

Permuting layer 2

Permuting layer 3

...

model.embed_tokens.weight -> token_embd.weight | BF16 | [64000, 7168]

model.layers.0.self_attn.q_proj.weight -> blk.0.attn_q.weight | BF16 | [7168, 7168]

model.layers.0.self_attn.k_proj.weight -> blk.0.attn_k.weight | BF16 | [1024, 7168]

...

model.layers.59.post_attention_layernorm.weight -> blk.59.ffn_norm.weight | BF16 | [7168]

model.norm.weight -> output_norm.weight | BF16 | [7168]

lm_head.weight -> output.weight | BF16 | [64000, 7168]

Writing ../playground/01-ai/Yi-34B/ggml-model-q8_0.gguf, format 7

gguf: This GGUF file is for Little Endian only

gguf: Setting special token type bos to 1

gguf: Setting special token type eos to 2

gguf: Setting special token type unk to 0

gguf: Setting special token type pad to 0

gguf: Setting add_bos_token to False

gguf: Setting add_eos_token to False

[ 1/543] Writing tensor token_embd.weight | size 64000 x 7168 | type Q8_0 | T+ 22

[ 2/543] Writing tensor blk.0.attn_q.weight | size 7168 x 7168 | type Q8_0 | T+ 22

[ 3/543] Writing tensor blk.0.attn_k.weight | size 1024 x 7168 | type Q8_0 | T+ 22

...

[541/543] Writing tensor blk.59.ffn_norm.weight | size 7168 | type F32 | T+ 484

[542/543] Writing tensor output_norm.weight | size 7168 | type F32 | T+ 484

[543/543] Writing tensor output.weight | size 64000 x 7168 | type Q8_0 | T+ 499

Wrote ../playground/01-ai/Yi-34B/ggml-model-q8_0.gguf

上面日志中最后出现的 ggml-model-q8_0.gguf 文件,就是我们转换生成的 8 位量化的 GGUF 模型文件啦。

为了能够运行和实践这个程序,我们还需要构建 llama.cpp 中的可执行程序:

# 构建项目可执行文件

make -j LLAMA_CUBLAS=1

当构建执行完毕之后,我们可以在项目目录中找到 server 可执行文件,我们可以使用下面的命令执行这个程序,来启动一个轻量、快速的 ChatBot 服务:

./server --ctx-size 2048 --host 0.0.0.0 --n-gpu-layers 64 --model ../playground/01-ai/Yi-34B/ggml-model-q8_0.gguf

命令执行完毕,我们将能够看到类似下面的日志输出:

ggml_init_cublas: GGML_CUDA_FORCE_MMQ: no

ggml_init_cublas: CUDA_USE_TENSOR_CORES: yes

ggml_init_cublas: found 8 CUDA devices:

Device 0: NVIDIA A800-SXM4-80GB, compute capability 8.0

Device 1: NVIDIA A800-SXM4-80GB, compute capability 8.0

Device 2: NVIDIA A800-SXM4-80GB, compute capability 8.0

Device 3: NVIDIA A800-SXM4-80GB, compute capability 8.0

Device 4: NVIDIA A800-SXM4-80GB, compute capability 8.0

Device 5: NVIDIA A800-SXM4-80GB, compute capability 8.0

Device 6: NVIDIA A800-SXM4-80GB, compute capability 8.0

Device 7: NVIDIA A800-SXM4-80GB, compute capability 8.0

{"timestamp":1702166275,"level":"INFO","function":"main","line":2652,"message":"build info","build":1620,"commit":"fe680e3"}

{"timestamp":1702166275,"level":"INFO","function":"main","line":2655,"message":"system info","n_threads":54,"n_threads_batch":-1,"total_threads":109,"system_info":"AVX = 1 | AVX2 = 1 | AVX512 = 1 | AVX512_VBMI = 1 | AVX512_VNNI = 1 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 | "}

llama_model_loader: loaded meta data with 22 key-value pairs and 543 tensors from ../playground/01-ai/Yi-34B/ggml-model-q8_0.gguf (version GGUF V3 (latest))

llama_model_loader: - tensor 0: token_embd.weight q8_0 [ 7168, 64000, 1, 1 ]

llama_model_loader: - tensor 1: blk.0.attn_q.weight q8_0 [ 7168, 7168, 1, 1 ]

llama_model_loader: - tensor 2: blk.0.attn_k.weight q8_0 [ 7168, 1024, 1, 1 ]

...

llama_model_loader: - tensor 540: blk.59.ffn_norm.weight f32 [ 7168, 1, 1, 1 ]

llama_model_loader: - tensor 541: output_norm.weight f32 [ 7168, 1, 1, 1 ]

llama_model_loader: - tensor 542: output.weight q8_0 [ 7168, 64000, 1, 1 ]

llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.

llama_model_loader: - kv 0: general.architecture str = llama

llama_model_loader: - kv 1: general.name str = LLaMA v2

llama_model_loader: - kv 2: llama.context_length u32 = 4096

llama_model_loader: - kv 3: llama.embedding_length u32 = 7168

...

llama_model_loader: - type f32: 121 tensors

llama_model_loader: - type q8_0: 422 tensors

llm_load_vocab: mismatch in special tokens definition ( 498/64000 vs 267/64000 ).

llm_load_print_meta: format = GGUF V3 (latest)

llm_load_print_meta: arch = llama

llm_load_print_meta: vocab type = SPM

llm_load_print_meta: n_vocab = 64000

...

llm_load_tensors: using CUDA for GPU acceleration

llm_load_tensors: mem required = 465.04 MiB

llm_load_tensors: offloading 60 repeating layers to GPU

llm_load_tensors: offloading non-repeating layers to GPU

llm_load_tensors: offloaded 61/61 layers to GPU

llm_load_tensors: VRAM used: 34383.15 MiB

...................................................................................................

llama_new_context_with_model: n_ctx = 2048

llama_new_context_with_model: freq_base = 5000000.0

llama_new_context_with_model: freq_scale = 1

llama_kv_cache_init: VRAM kv self = 480.00 MB

llama_new_context_with_model: KV self size = 480.00 MiB, K (f16): 240.00 MiB, V (f16): 240.00 MiB

llama_build_graph: non-view tensors processed: 1264/1264

llama_new_context_with_model: compute buffer total size = 273.07 MiB

llama_new_context_with_model: VRAM scratch buffer: 270.00 MiB

llama_new_context_with_model: total VRAM used: 35133.16 MiB (model: 34383.15 MiB, context: 750.00 MiB)

Available slots:

-> Slot 0 - max context: 2048

llama server listening at http://0.0.0.0:8080

在上面的日志中,我们能够清晰的看到当前运行程序的环境(有多少张卡、每张卡有多少显存、模型的每一层的具体参数、数据量、文件大小、模型文件的基础信息、模型加载所实际消耗的内存和显存资源等等。

相比较上文中的 PyTorch 模型,是不是“可解释性”强了非常多呢?

当我们访问上面日志中最后一行输出的地址 http://0.0.0.0:8080,就能够看到 llama.cpp 的默认界面啦。

在使用这个 Web 界面开始聊天之前。我们还是先进行一些配置和调整。 再次使用了上文中带有“笔误”的 Prompt 内容,并且尽可能的将模型参数配置和上文中调整到一样。但是,默认的 llama.cpp 程序中,还是和上文中的参数有一些不同:

Predictions: 对应上文中的 max_tokens,在不对 llama.cpp 做调整之前,默认最大参数为 2K。 Min-P sampling:上文中 transformers 配置中并没有这个参数,关于这个参数,目前社区正在讨论是否要添加到库中(#27670)。 当我们点击按钮进行提交后,将会发现模型的输出效率相比之前直接加载官方模型,速度快了许多倍。

当然,最直观以及和推理成本最相关的显存需求,也得到了明显下降:从原本需要 70~80GB 的显存,降低到了 46GB。

或许你会说,这不是还是一张卡的水平嘛,其实不然。一来即使都是使用 80GB 显存的硬件,前者 70~80GB,已经接近显卡最大容量,一旦程序数据量激增,很容易发生 OOM,导致程序 Crash。而后者还留有几十 GB 的“冗余”。二来,46GB 是我们将模型全部都加载到显卡中啦。其实,我们也可以根据自己的实际情况,将模型层中的一半或者更多都卸载到内存中,这样对显卡的显存需求就能够有质的下降 啦。

比如,我们可以将执行加载模型的命令进行调整,将其中的 --n-gpu-layers 64 调整为模型的一半尺寸 --n-gpu-layers 32。此时在看模型的显存消耗也就降低到了一半,就只需要 26GB 了,相比较最初的官方版本,显存立省 2/3:

在这个例子中,模型的输出的结果怎么说呢?前 1241 个字,还是在讲故事,后面发现能偷懒了,就和我们在玩“从前有座山、山上有座庙、庙里有个小和尚…”的循环把戏了。

不过,你还记得前文提到的“毛胚房”概念嘛?想要追求效果,还是要“精装修一下”的。

其他:LLM 榜单头部模型的分类和血缘关系 我将目前 HuggingFace 社区中排名较高的前 30 个模型项目进行了梳理和分类,希望对你有帮助 :

Yi-34B

- 1: fblgit/una-xaberius-34b-v1beta (LLaMA-Yi-34B)

- 3: SUSTech/SUS-Chat-34B

- 6: bhenrym14/platypus-yi-34b (chargoddard/Yi-34B-Llama)

- 9: brucethemoose/CapyTessBorosYi-34B-200K-DARE-Ties

- 10: chargoddard/Yi-34B-Llama

- 22: kyujinpy/PlatYi-34B-Q

- 29: 01-ai/Yi-34B

QWen 72B

- 2: Qwen/Qwen-72B

DeepSeek 67B

- 4: deepseek-ai/deepseek-llm-67b-chat

Yi-34B 200K

- 5: migtissera/Tess-M-Creative-v1.0

- 11: 01-ai/Yi-34B-200K

- 14: brucethemoose/Capybara-Tess-Yi-34B-200K

- 16: Mihaiii/Pallas-0.2

- 19: migtissera/Tess-34B-v1.4

- 24: migtissera/Tess-M-v1.1

- 25: migtissera/Tess-M-v1.3

Llama2 70B

- 7: DiscoResearch/DiscoLM-70b

- 12: ehartford/dolphin-2.2-70b

- 15: KaeriJenti/kaori-70b-v1

- 20: upstage/SOLAR-0-70b-16bit

- 21: ICBU-NPU/FashionGPT-70B-V1.1

- 23: sequelbox/StellarBright

- 28: MayaPH/GodziLLa2-70B

Mistral-7B

- 8: Q-bert/MetaMath-Cybertron-Starling

- 13: Q-bert/MetaMath-Cybertron

- 17: perlthoughts/Chupacabra-7B-v2.01

- 18: HyperbeeAI/Tulpar-7b-v2

- 26: fblgit/una-cybertron-7b-v2-bf16

- 27: fblgit/una-cybertron-7b-v1-fp16

如果你和我一样,使用 Docker 环境折腾、学习和用于生产。那么,我推荐你使用 Nvidia 家提供的深度学习环境 nvcr.io/nvidia/pytorch:23.10-py3 作为基础镜像,其中的 CUDA 版本经常效率比公开的开源社区版本要跑的更快一些:

FROM nvcr.io/nvidia/pytorch:23.10-py3

RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple && \

pip3 install --upgrade pip # enable PEP 660 support

RUN sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list && \

sed -i 's/security.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list

WORKDIR /app

RUN pip3 install transformers==4.35.0 huggingface-hub==0.17.3 hf_transfer==0.1.4

RUN apt-get update && apt-get install cmake -y && apt-get autoremove -y

将上面的内容保存为 Dockerfile,然后使用下面的命令来完成稍后使用的镜像的构建(项目中的相关文件保存在 soulteary/docker-yi-runtime/docker/Dockerfile):

docker build -t soulteary/yi-34b-runtime:20231126 .

构建完毕运行环境后,我们可以使用下面的命令进入容器环境,继续折腾:

docker run --rm -it -p 8080:8080 --gpus all --ipc=host --ulimit memlock=-1 -v `pwd`:/app soulteary/yi-34b-runtime:20231126 bash

进入容器后,我们可以使用熟悉的 nvidia-smi 查看“显卡”是否能够被正确使用和使用:

Sun Nov 26 08:41:52 2023

+-----------------------------------------------------------------------------+

| NVIDIA-SMI 525.147.05 Driver Version: 525.147.05 CUDA Version: 12.0 |

|-------------------------------+----------------------+----------------------+

| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |

| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |

| | | MIG M. |

|===============================+======================+======================|

| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 Off | Off |

| 31% 33C P8 27W / 450W | 93MiB / 24564MiB | 0% Default |

| | | N/A |

+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+

| Processes: |

| GPU GI CI PID Type Process name GPU Memory |

| ID ID Usage |

|=============================================================================|

+-----------------------------------------------------------------------------+

因为我们选择的是 Nvidia 提供的环境,所以 nvcc 是就绪的。但是如果你是在 Bare Metal 中运行,记得解决 nvcc 的安装,保持和显卡驱动大版本一致,下文中编译 llama.cpp 需要 nvcc 是可用的:

# nvcc --version

nvcc: NVIDIA (R) Cuda compiler driver

Copyright (c) 2005-2023 NVIDIA Corporation

Built on Tue_Aug_15_22:02:13_PDT_2023

Cuda compilation tools, release 12.2, V12.2.140

Build cuda_12.2.r12.2/compiler.33191640_0

下载合适的模型文件 零一万物的官方模型发布页面是 01-ai/Yi-34B,里面包含了两种格式的模型,如果你想转换模型格式为我们可以运行的 ggml 或者 gguf 格式,可以参考前文中的文章。如果你懒得进行这个量化步骤,可以直接下载 HuggingFace 社区其他社区用户量化好的版本:

TheBloke/Capybara-Tess-Yi-34B-200K-DARE-Ties-GGUF NousResearch/Nous-Capybara-34B 等等 如果你想快速下载可靠的模型,可以使用下面的命令:

# 我们可以使用 Huggingface Cli 来下载模型

huggingface-cli download --resume-download --local-dir-use-symlinks False ${模型在huggingface上的名字} ${模型文件名}.gguf --local-dir ${模型存储路径}

# 你也可以使用下面的命令,通过社区用户搭建的减速器来下载

HF_ENDPOINT=https://hf-mirror.com HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli download --resume-download --local-dir-use-symlinks False ${模型在huggingface上的名字} ${模型文件名}.gguf --local-dir ${模型存储路径}

测试选择的是 The Bloke 提供的开源模型,分别选择了 Q4 和 Q5 两个尺寸的量化模型,这两个模型有一个有趣的差异,一个恰好放的进 24G 显存的卡里,一个恰好放不进去:

# Capybara Tess Yi 34B 200K Dare Ties - GGUF

# https://huggingface.co/TheBloke/Capybara-Tess-Yi-34B-200K-DARE-Ties-GGUF

TheBloke/Capybara-Tess-Yi-34B-200K-DARE-Ties-GGUF capybara-tess-yi-34b-200k-dare-ties.Q4_K_M.gguf

# Nous Capybara 34B - GGUF

# https://huggingface.co/TheBloke/Nous-Capybara-34B-GGUF

TheBloke/Nous-Capybara-34B-GGUF nous-capybara-34b.Q5_K_M.gguf

下载模型参考上文中的说明,不难得到下载命令,如果你要下载其他的模型,可以参考并进行调整:

# 比如

huggingface-cli download --resume-download --local-dir-use-symlinks False TheBloke/Capybara-Tess-Yi-34B-200K-DARE-Ties-GGUF capybara-tess-yi-34b-200k-dare-ties.Q4_K_M.gguf --local-dir ./models/TheBloke/Capybara-Tess-Yi-34B-200K-DARE-Ties-GGUF

模型保存目录结构如下:

models

└── TheBloke

├── Capybara-Tess-Yi-34B-200K-DARE-Ties-GGUF

│ └── capybara-tess-yi-34b-200k-dare-ties.Q4_K_M.gguf

└── Nous-Capybara-34B-GGUF

└── nous-capybara-34b.Q5_K_M.gguf

完成了模型的下载之后,我们就可以进行最后一项准备工作了。

编译使用 GPU 的 llama.cpp 在解决完毕运行环境的问题后,我们需要手动下载 llama.cpp 的代码,并做一些目录准备:

# 下载代码

git clone https://github.com/ggerganov/llama.cpp.git

# 进入代码目录

cd llama.cpp/

使用 make -j LLAMA_CUBLAS=1 对 llama.cpp 进行构建,启用 CUDA 功能,以便让程序支持使用显卡资源,耐心等待构建完毕:

# make -j LLAMA_CUBLAS=1

I llama.cpp build info:

I UNAME_S: Linux

I UNAME_P: x86_64

I UNAME_M: x86_64

I CFLAGS: -I. -Icommon -D_XOPEN_SOURCE=600 -D_GNU_SOURCE -DNDEBUG -DGGML_USE_CUBLAS -I/usr/local/cuda/include -I/opt/cuda/include -I/targets/x86_64-linux/include -std=c11 -fPIC -O3 -Wall -Wextra -Wpedantic -Wcast-qual -Wno-unused-function -Wshadow -Wstrict-prototypes -Wpointer-arith -Wmissing-prototypes -Werror=implicit-int -Werror=implicit-function-declaration -Wdouble-promotion -pthread -march=native -mtune=native

...

g++ -I. -Icommon -D_XOPEN_SOURCE=600 -D_GNU_SOURCE -DNDEBUG -DGGML_USE_CUBLAS -I/usr/local/cuda/include -I/opt/cuda/include -I/targets/x86_64-linux/include -std=c++11 -fPIC -O3 -Wall -Wextra -Wpedantic -Wcast-qual -Wno-unused-function -Wmissing-declarations -Wmissing-noreturn -pthread -Wno-array-bounds -Wno-format-truncation -Wextra-semi -march=native -mtune=native examples/export-lora/export-lora.cpp ggml.o llama.o common.o sampling.o grammar-parser.o build-info.o ggml-cuda.o ggml-alloc.o ggml-backend.o ggml-quants.o -o export-lora -lcublas -lculibos -lcudart -lcublasLt -lpthread -ldl -lrt -L/usr/local/cuda/lib64 -L/opt/cuda/lib64 -L/targets/x86_64-linux/lib

==== Run ./main -h for help. ====

当看到 Run ./main -h for help 这条日志的时候,我们可以看到当前目录多出了很多文件和新的可执行文件:

baby-llama benchmark finetune main quantize simple test-grammar-parser test-rope test-tokenizer-1-bpe vdot

batched convert-llama2c-to-ggml infill parallel quantize-stats speculative test-llama-grammar test-sampling test-tokenizer-1-llama

batched-bench embedding llama-bench perplexity save-load-state test-c test-quantize-fns test-tokenizer-0-falcon tokenize

beam-search export-lora llava-cli q8dot server test-grad0 test-quantize-perf test-tokenizer-0-llama train-text-from-scratch

这里面有许多有趣的工具,不过网上关于它们的介绍其实不多,更多的都是关于主程序 main 的简单使用。后面的文章,有机会我们再来逐步展开这些工具中更有趣的玩法。

本篇文章,我们主要使用 server 程序,来跳过各种“分数”,一窥真实的模型能力。为了方便后面使用,我们可以将这个纯粹的二进制文件复制到我们容器中的 /app 目录中:

cp server /app/

开始测试前的完整目录结构如下:

├── models

│ └── TheBloke

│ ├── Capybara-Tess-Yi-34B-200K-DARE-Ties-GGUF

│ │ └── capybara-tess-yi-34b-200k-dare-ties.Q4_K_M.gguf

│ └── Nous-Capybara-34B-GGUF

│ └── nous-capybara-34b.Q5_K_M.gguf

└── server

使用 llama.cpp 运行 34B 大模型 我们先来使用比较保守的策略来运行模型:

./server --ctx-size 2048 --host 0.0.0.0 --n-gpu-layers 32 --model ./models/TheBloke/Nous-Capybara-34B-GGUF/nous-capybara-34b.Q5_K_M.gguf

上面的命令中,我们在加载模型的时候,设置了上下文尺寸为 2k,将模型中的 32 层发送到 GPU 中,启动一个能够被容器外部访问的服务。

稍等片刻,当我们看到类似下面的内容的时候,服务就可以正常使用啦:

llama_new_context_with_model: VRAM scratch buffer: 270.00 MiB

llama_new_context_with_model: total VRAM used: 270.00 MiB (model: 0.00 MiB, context: 270.00 MiB)

Available slots:

-> Slot 0 - max context: 2048

llama server listening at http://0.0.0.0:8080

{"timestamp":1700997080,"level":"INFO","function":"main","line":2917,"message":"HTTP server listening","hostname":"0.0.0.0","port":8080}

通过访问 http://主机IP:8080 ,我们能够访问到一个简洁的 Web 界面。 调用模型时,不同的参数将会带来不同的结果。为了保持简单,我们可以先暂时使用默认参数,并在在最下面的对话框中输入我们要模型处理的内容。 虽然在过往经验中,我们一般会对内容进行处理,得到更好的效果,但其实大模型有很强的“容错性”,我们也可以偷懒,不对模型要处理的内容进行“提纯”,我提交的内容中就包含了很多 HTML 占位符:许多 “图片” 字符串。 因为使用 “保守策略”,程序刚刚启动的时候,被灌进显卡内的模型并不大,只有 13 GB。(当前,你也可以调整的更多或者更少)

|===============================+======================+======================|

| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 Off | Off |

| 31% 38C P2 64W / 450W | 13149MiB / 24564MiB | 0% Default |

| | | N/A |

+-------------------------------+----------------------+----------------------+

当我们点击上面的 “Send” 按钮后,模型会开始处理任务,不过毕竟是用 “Hybird” 方式处理 340 亿参数量的模型,等待时间会久一些。 在程序日志中,我们能够看到模型程序到底接收了多少内容:

slot 0 is processing [task id: 0]

slot 0 : in cache: 0 tokens | to process: 1416 tokens

slot 0 : kv cache rm - [0, end)

如果此时我们查看 CPU 使用状况,将能够看到多数 CPU 核心都在“卖力干活”: 稍等片刻,就能够看到模型开始持续的输出内容啦: 等待程序执行完毕,我们将得到模型的处理性能和详细细节。

print_timings: prompt eval time = 12049.71 ms / 1416 tokens ( 8.51 ms per token, 117.51 tokens per second)

print_timings: eval time = 115381.73 ms / 400 runs ( 288.45 ms per token, 3.47 tokens per second)

print_timings: total time = 127431.44 ms

slot 0 released (1817 tokens in cache)

这里能够看出模型的摘要总结能力还是很强的。 不过,第一轮对话执行之后,用来分摊 CPU 和内存压力的显存,使用量接近 15GB 显存啦。

|===============================+======================+======================|

| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 Off | Off |

| 31% 38C P8 28W / 450W | 15277MiB / 24564MiB | 0% Default |

| | | N/A |

+-------------------------------+----------------------+----------------------+

当然,就这么泛泛的测试一轮,未免有点儿戏。我们来进行对轮对话测试。这里为了考验模型的基础能力,所有的问题我都不会加“问号”,并且尽量用代词而非具体的内容来和它交流。 除了最后一个问题,因为没有和“李开复”交流过,不确定这个口吻像不像,只能够看出来模型突然画风变的高冷,以大佬姿态说出了一段话外。其他的回答,我个人还是比较满意的。

当然,因为我们使用了“资源保守策略”,在启动程序的时候,只将模型的 32 层放在了显卡里,所以模型输出内容的速度只有 3.4 tokens/sec 左右。

如果我们选择完全使用 CPU,速度则只剩下了 1.8 tokens/sec 左右。 为了提升模型吞吐速度,我们来做个简单的性能优化。

性能提升技巧:调整 llama.cpp 加载模型层数 在目前的普通硬件环境中,一般来说显卡的带宽和数据交换性能远远高于内存,所以我们可以将模型更多的往显卡里塞,来减少程序在几十 GB 模型程序和数据中寻找我们需要的答案的时间。

当然,完全将模型放在显存里是有些困难的(除非进行性能损失比较大的量化)。在上面的测试中,我们选择的是将模型的一半放在显卡里,通过观察我们可以得出模型在运行之后,至少会额外占据 1GB 的显存,所以我们可以增大 --n-gpu-layers 到合理的程度。

我这里的选择是设置为 55 层:

./server --ctx-size 2048 --host 0.0.0.0 --n-gpu-layers 55 --model ./models/TheBloke/Nous-Capybara-34B-GGUF/nous-capybara-34b.Q5_K_M.gguf

程序启动过程中,我们可以知道这将会在初始化过程中,占据显卡的 20GB 显存。

llm_load_tensors: using CUDA for GPU acceleration

llm_load_tensors: mem required = 2586.53 MiB

llm_load_tensors: offloading 55 repeating layers to GPU

llm_load_tensors: offloaded 55/63 layers to GPU

llm_load_tensors: VRAM used: 20607.34 MiB

程序实际运行完毕后,我们在 nvidia-smi 中得到的资源占用情况会更多一些:

|===============================+======================+======================|

| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 Off | Off |

| 31% 35C P8 27W / 450W | 21913MiB / 24564MiB | 0% Default |

| | | N/A |

+-------------------------------+----------------------+----------------------+

然后我们再次使用相同的机器之心发布的内容,来进行模型测试,你会发现模型的处理速度快了非常多,比之前大概快了接近 3 倍。 当然,我们还是多问几轮,看看情况。

速度比之前稍微慢了一些,变成了 8.x token/sec,整体回答我都比较满意。除了最后一条,列举实体的时候,把 meta 也列了进去,虽然没有把 meta 看做人,回答也可圈可点,但是文本的并列关系里,其他三项都是人名呐。

性能提升技巧:选择更小巧的模型 上文中我们使用的是稍微大一些的模型,如果我们选择恰好放的进显卡的 Q4 版本的量化模型,可以在启动的时候,设置加载层数完全等于模型层数:

./server --ctx-size 2048 --host 0.0.0.0 --n-gpu-layers 63 --model ./models/TheBloke/Capybara-Tess-Yi-34B-200K-DARE-Ties-GGUF/capybara-tess-yi-34b-200k-dare-ties.Q4_K_M.gguf

同样,模型加载的时候,我们能够看到资源使用状况:

llm_load_tensors: ggml ctx size = 0.20 MiB

llm_load_tensors: using CUDA for GPU acceleration

llm_load_tensors: mem required = 246.29 MiB

llm_load_tensors: offloading 60 repeating layers to GPU

llm_load_tensors: offloading non-repeating layers to GPU

llm_load_tensors: offloaded 63/63 layers to GPU

llm_load_tensors: VRAM used: 19454.15 MiB

使用 nvidia-smi 查看资源,显存使用了 21GB。

|===============================+======================+======================|

| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 Off | Off |

| 31% 36C P8 27W / 450W | 21123MiB / 24564MiB | 0% Default |

| | | N/A |

+-------------------------------+----------------------+----------------------+

我们还是用相同的文章内容进行测试,速度已经非常棒了。 当然,这个模型的特点是支持 200K 文本。所以,如果你有多张卡或者大显存的资源,不妨调整 --ctx-size 到 200K 试试看。

我们再进行一个简单的测试,分别并要求模型输出两种不同的指定格式: 我们在尝试不给出提示的前提下,要求模型输出 JSON 格式:

关于Yi-34B语言模型的微调过程,有以下信息:

Hugging Face模型库:在Hugging Face的模型库中可以找到01-ai/Yi-34B模型,该模型具有丰富的微调能力。具体细节可以在链接中找到。链接 Medium文章:有一篇Medium文章介绍了针对CodeLlama 34B模型的微调过程。在单个4090 GPU上进行微调需要将LoRa rank降至32,并将最大序列长度设置为512以适应显存的限制。具体细节可以在链接中找到。链接 GitHub项目:在GitHub上有一个名为shahabmohi/Yi-34B-Chat的项目,包含了一系列经过微调的大型语言模型。这些模型基于之前发布的基础模型,并进行了量化和微调。具体细节可以在链接中找到。链接

以下是关于Yi-34B微调过程的一些详细文档和资源:

Hugging Face模型库:Hugging Face模型库的01-ai/Yi-34B页面提供了关于Yi-34B的详细信息和文档,包括微调能力和使用案例。您可以在该页面找到有关Yi-34B微调的更多信息。链接 NVIDIA NGC文档:NVIDIA NGC的Yi-34B页面提供了关于Yi-34B的文档,包括介绍、微调以及上下文窗口等详细信息。您可以在该页面了解有关Yi-34B微调过程的更多信息。链接 GitHub项目:GitHub上的shahabmohi/Yi-34B-Chat项目提供了一系列经过微调的大型语言模型,其中包括Yi-34B-Chat和其他模型。您可以在该项目中找到与Yi-34B微调相关的文档和资源。链接 Medium文章:Medium上有一篇名为"Fine-Tuning CodeLlama 34B for Chat"的文章,介绍了在单个4090 GPU上微调CodeLlama 34B模型的过程和技巧。您可以在该文章中了解到Yi-34B微调的一些详细信息。链接 开源项目文档:01.AI的GitHub页面中的"Building the Next Generation of Open-Source and Bilingual LLMs"部分提到了Yi模型的微调过程以及相关要求和资源。您可以在该文档中获得更多关于Yi-34B微调的信息。链接

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: