[ggerganov/llama.cpp]在 M2 Mac 上使用简单自引用语法时出现段错误

2024-03-22 222 views
0

嘿伙计们,首先非常感谢这个令人惊叹的项目,特别是语法支持 - 老实说感觉就像魔法<3

我受到@ggerganov 转发的这条推文的启发,尝试将树保姆语法转换为 GBNF 形式。经过一些小问题后,我有一个脚本创建了可以由 llama.cpp 解析的语法,但不幸的是我现在遇到了分段错误。

我将其与这个简单的示例语法隔离:

root ::=  expression
expression ::= (integer | binary-operator)
integer ::=  [0-9]+
binary-operator ::=  expression  ("+"  |  "-")  expression

这是段错误。我已经包含了 llama.cpp 的完整参数和缩短的输出:

% ./main --grammar-file elixir.gbnf --model mistral-7b-instruct-v0.1.Q6_K.gguf -t 7 -b 24 -n -1 --temp 0 -ngl 1 -ins

Log start
main: build = 1428 (6961c4b)
main: built with Apple clang version 14.0.3 (clang-1403.0.22.14.1) for arm64-apple-darwin22.6.0
main: seed  = 1698347229

[... the usual loading of the model output...]

== Running in interactive mode. ==
 - Press Ctrl+C to interject at any time.
 - Press Return to return control to LLaMa.
 - To return control without starting a new line, end your input with '/'.
 - If you want to submit another line, end your input with '\'.

zsh: segmentation fault  ./main --grammar-file  --color --model  -t 7 -b 24 -n -1 --temp 0 -ngl 1 -ins

其他一些观察结果:

  • shell 在出现段错误之前挂起几秒钟,表明可能存在无限循环
  • 如果语法不是自引用的(例如,将最后一行替换为binary-operator ::= integer ("+" | "-") integer
  • 无论我使用哪种模型,都会发生这种情况,并且如果不使用语法就不会发生这种情况
  • 这不是语法分析错误。这些会在模型加载之前发生并打印特定的错误消息

任何帮助将非常感激。如果我们能够强制 llama.cpp 输出 Tree Sitter 支持的任何编程语言,那就太酷了。但它们中的大多数都包含这种自我指涉的符号。

回答

7

我还没有尝试过自引用语法 - 可能这是 atm 不支持的。正在 Ping @ejones

5

如果有趣的话,我在这里写了更多关于上下文的内容: https: //github.com/jonastemplestein/tree_sitter_grammar_to_gbnf

大多数情况下,如果/当 llama.cpp 添加对此类语法的支持时,我可以从上次停下的地方继续

非常感谢您研究这个<3

2

是的,这里的问题是左递归binary-operator -> grammar -> binary-operator。语法实现使用一个简单的自顶向下解析器,它不支持这一点。它确实会导致无限循环。

换句话说,可以存在递归,只是不能出现在规则体的第一项中。所以这个语法是有效的,例如:

root ::= expression
expression ::= integer binary-operator?
integer ::= [0-9]+
binary-operator ::= ("+" | "-") expression

在这种情况下,通过移出integerbinary-operator将其重新表述为后缀),我们避免了左递归。

现在,我还没有研究过 Tree Sitter,我不知道以这种方式自动生成或转换语法有多容易。

7

哦,太棒了,非常感谢。我可以在左侧插入一个空格符号,玩具语法就可以工作:

root ::=  expression
expression ::= (integer | binary-operator)
integer ::=  [0-9]+
binary-operator ::=  ws expression  ("+"  |  "-")  expression
ws ::= [ \n\t]+

我已经在 Elixir 语法中的各种定义前面添加了空格符号,现在我可以进入输入提示了!令人兴奋!

不幸的是 llama.cpp 仍然没有生成 Elixir 代码。它只是不断给出单个单词的答案或打印无限系列的换行符。

llama.cpp 生成的每个后续标记都比第一个标记花费更长的时间,这表明每次都在语法中解析整个输出字符串,而不是在我们进行过程中标记输出并存储该状态。真的吗?

在这种情况下,我们可能距离使用形式语法强制 llama.cpp 仅输出高级编程语言中的有效代码还有一段距离。

如果您感兴趣,您可以在此存储库(elixir_no_left_recursion.gbnf) 中找到导致此行为的完整 Elixir 语法以及更多注释

5

向我建议每次都会在语法中解析整个输出字符串,

它不是。我们确实只是在进行过程中存储解析状态。我必须调查一下这个。我想到的一件事是:我们存储所有可能的解析状态,这对于确定性或大部分确定性语法来说是合理的。如果语法在某些方面是模糊的或不确定的,这可能会导致复合效应;我不知道这里是否是这种情况。

另一件事是,肯定有某些病理情况会导致令牌采样缓慢,但我还没有机会研究这一点。但当我发现这更多是突然的事情而不是缓慢的积累。

打印无限系列的换行符。

这是我们在语法中观察到的空白,通常我们必须限制模型允许添加的空白量。

仍然没有生成 Elixir 代码

请注意,模型不知道语法约束,因此如果语法与它的意图相差很大,它就会感到困惑。查看您的示例存储库,看起来这个指令模型正在尝试编写解释,这些解释有点被强制写入 Elixir 代码中。也许您需要将代码块语法(```等)合并到您的语法中,或者让模型先说话。一般来说,我发现一个好的方法是查看模型在没有约束的情况下做什么,并围绕它构建语法。但这可能与树保姆语法的一刀切翻译的目标相矛盾。

此外,一旦你接受语法限制,你的语法基本上需要描述完整的语言。或者您需要向模型解释您的目标子集。同样,模型会认为它可以做任何 Elixir 的事情,如果语法不能识别这一点,生成可能会脱轨。

我认为语法的约束和完整性之间的这种紧张关系更多地是将它们应用于高级语言的障碍。我认为最新的模型在生成代码方面也相当出色?根据用例,不清楚通过指定编程语言语法可以增加多少价值。采用“生成一个代码块,我将获取其中的任何内容”之类的方法可能更有意义。

1

非常感谢您的周到回复,这给了我一些探索的途径。

我可以从语法中删除空格,并使用代码块和不同的提示

如果我能做到这一点,我将尝试使用一些更好的测试用例来调试随着时间的推移而逐渐放缓的情况。

关于这样一个模型的需求,我同意对于在训练中见过很多 Elixir 的大型模型,你可以只要求它输出一个代码块。但对于非常小的模型,特别是那些没有在 Elixir 上进行微调的模型(因为基本模型通常没有像 python、javascript 等那样接触 Elixir)

3

最后一个想法 - 我听到提到定制 WASM 采样器的想法。这件事正在处理中吗?

如果是这样,对于许多编程语言来说,同时完成此任务的最简单方法实际上可能是构建一种树守护者 WASM,它公开一个接口,llama.cpp 可以查询该接口,以将生成的令牌限制为语法上合法的令牌。

(对从原始无限循环中劫持线程表示歉意 - 让我知道我是否应该在其他地方放弃这个想法?)