除了权重之外,GGUF 中还有什么——还缺少什么?

2026-05-14 1 阅读 bashbjorn
除了权重之外,GGUF 中还缺少什么? GGUF 是 llama.cpp 用于语言模型的文件格式。 GGUF 真正巧妙的一点是它只是一个文件。将此与 Huggingface 上的典型 safetensors 存储库进行比较,其中散布着一堆必要的 JSON 文件 - 或者与典型的 ollama 模型相比,这是一个 OCI,内部包含 json 层、go 模板等。内容大致相同,但 GGUF 将所有这些内容保存在一个文件中,使其更加符合人体工程学。但这是什么东西,它涵盖了所需的一切吗?聊天模板 对话语言模型是根据遵循特定格式的序列进行训练的,这有点像对话。例如,Gemma4 的格式如下所示: <|turn>user Hi There! <|turn>model Hi There, how do I help you today? ...而 LFM2 的格式模板如下所示: <|im_start|>user Hi there!<|im_end|> <|im_start|>assistant Hi there, how can I help you today?<|im_end|> ..这只是一个基本示例。一旦我们开始添加奇特的功能,比如如何以及何时格式化推理块,如何呈现工具描述、工具调用及其响应,以及如何对多媒体消息(图像、音频、视频等)进行编码,事情就会变得更加复杂。所有这些都由聊天模板(jinja2 模板语言中的脚本)处理。例如,请参阅 Gemma 4 附带的聊天模板。默认聊天模板存储在 GGUF 元数据中的 tokenizer.chat_template 键下。一个模型可以有多个聊天模板。例如。一种支持工具调用,另一种不支持。最常见的模型附带一个单一的整体聊天模板,这只会在指定工具时打扰工具调用内容,但您确实需要在某些模型中查找特定于工具的聊天模板。 Jinja2 是一种编程语言,毫无疑问 - 它有循环、条件、赋值、列表、字典等 - 因此任何会话式 LLM 应用程序都必须提供一个编程语言解释器,能够在每次添加新消息时运行 gemma 附带的约 250 行 jinja 脚本之类的程序。 Huggingface Transformers 使用 jinja2 (经典的 Python 库),llama.cpp 的 llama-server 和 llama-cli 使用它们自己的 jinja 实现(不要与 libllama API 中公开的有点令人困惑的 llama_chat_apply_template 混淆,后者直接在 C++ 中硬编码一些聊天格式——这是真正的 jinja 实现落地之前的迷人遗迹),而NobodyWho 使用 minijinja ,这是一个jinja 由其原始创建者用纯 rust 重新实现(不要与 minja 混淆,minja 是一个曾经被 llama.cpp 使用的极简主义 jinja 库)。这些 jinja 实现之间存在相当大的性能差异。但聊天模板并不完全是本地 LLM 应用程序的性能瓶颈,因此不值得争论。特殊令牌语言模型将很容易地永远为您提供的任何令牌序列输出下一个令牌 - 因此我们需要某种方法来阻止它们。典型的解决方案是某种序列结束标记。这个想法是每当模型发出这样的令牌时,推理引擎就会停止生成。这是特殊令牌的示例。特殊标记通常是比它们标记为的字母具有更广泛语义含义的标记。它们通常是不应该向用户显示的标记,尽管它们(通常)仍然具有文本表示形式,所以它们可以。例如,Gemma4 的一些标记: 标记 ID 文本表示 目的 1 序列结束,模型发出此信号以停止生成。 2 序列的开头,添加到输入之前。 46 <|tool_call> 标记工具调用的开始。 47 标记工具调用的结束。 105 <|turn> 对话轮次的开始。 106 会话结束。采样器配置语言模型输出下一个标记概率的分布。从这个分布中选择一个标记称为采样。最简单的方法是从加权分布中随机选择。但我们可以做得更多。事实证明,在选择具体标记之前,通过对概率分布应用一些变换,可以获得更好的结果。当研究实验室交付新模型时,它们通常会包含特定的推荐采样器配置。我经常看到人们从 Markdown 文件中复制粘贴这些值,以便从模型中获得更好的响应。为了节省用户这一步,我们开始将一小部分精选模型上传到我们的huggingface页面,并以我们自己想出的格式与推荐的采样器设置捆绑在一起。它确实有效,但这意味着每个模型都需要进行NobodyWho 端的转换才能发挥作用。令人高兴的是,最​​近添加的 GGUF 格式允许直接在模型文件中指定采样器链。这使得我们的自定义格式变得过时