1 ポイント 投稿者 GN⁺ 2023-08-15 | 1件のコメント | WhatsAppで共有
  • OutlinesはLLM向けの構造化出力ツールで、生成時に望む出力型を指定して、データがその構造に正確に一致することを保証する
  • パースや正規表現、壊れやすいコードで生成後に誤った出力を修正するのではなく、生成段階で有効な構造を直接強制する
  • 使用方法はmodel(prompt, output_type)の形式で、Literal["Yes", "No"]intPydantic modelのようなPythonの型体系に合わせた出力指定が可能
  • 対応する出力タイプは、事前定義された選択肢、関数シグネチャベースのFunction Calls、JSON/Pydanticスキーマ、正規表現パターン、文法(grammars)ベースの構造強制
  • モデル統合は、サーバー対応のvLLM・Ollama、ローカルモデル対応のtransformers・llama.cpp、API対応のOpenAI・Gemini・Dottxtに分かれる
  • 例のワークフローとしては、顧客メールをサポートチケットに変換、商品説明をカテゴリデータに変換、不完全なイベント説明から構造化情報または"I don't know"を返す、文書を事前定義カテゴリに分類、自然言語の会議依頼を関数パラメータに変換する、といったものがある
  • プロンプトテンプレートはJinjaベースのoutlines.Template.from_stringとファイル読み込みをサポートし、複雑なプロンプトをコードから分離して再利用する用途に向く
  • インストールはpip install outlinesで提供され、.txt APIは現在early accessの状態とされている

1件のコメント

 
GN⁺ 2023-08-15
Hacker News のコメント
  • このライブラリは、仕組みとしては語彙空間の一部をマスクし、時間ステップごとに効率よく進めるというシンプルなアイデアを使っているようで、素晴らしいと思う。
    ただ、ベースの LLM に出力構造をかぶせるライブラリを使ったことがある立場からすると、Llama2 のようなベースモデルで本当にうまく動くのかは気になる。
    私の経験では「まったくそうではない」に近く、実際に動くようにするには、特定用途向けの指示チューニングがかなり必要だった。
    さらに、指示チューニング済みモデルに対して生成中に事後的に状態空間をマスクするのは、結局のところ生成分布を変えることになるので、指示チューニングに悪影響を与える可能性もあるという点が、直感に反しているように見える。

    • あえて元の llama-2 を使う理由がよく分からない。HF Hub には llama-2 を指示ファインチューニングした強力なバージョンが非常に多くあり、そうしたモデルのほうがはるかにうまく仕事をするはずだ。たとえば Stability-AI の Beluga-2 がある: https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderb...
      2つ目の論点については、目的がたとえばモデルに JSON だけを生成させることなら、どの出力トークンを使えて、どれを使えないかを制限する方法で 100% 可能だ。
    • Llama 2 13B にはかなり感心していて、長く使ってみるほど、ローカル LLM のおもちゃ以上に実用性があるかもしれないと思うようになった。
      M2 Mac で GPU を使えるので、MLC 版を https://github.com/simonw/llm-mlc プラグインで使っている。
    • 少なくともコード生成では、トークンレベルの生成を誘導すると、ベースモデルでも大きく改善できた。
      論文 “Guiding Language Models of Code with Global Context using Monitors”(https://arxiv.org/abs/2306.10763) では Monitor Guided Decoding を提案しており、LLM を静的解析とつなげて、型の整合性があるコードを生成するよう誘導している。
      いかなるファインチューニングもなしに、特定の地点で静的解析によってトークンレベルの生成を誘導すると、コンパイル可能性と正解との一致度の両方で、生成コードの品質が大きく向上した。非常に小さなモデル(1.1B)でも、はるかに大きなモデル(175B)よりコンパイル可能なコードを多く作りつつ、正解一致度も改善した。
    • 「指示チューニング済みモデルで、生成中の事後マスキングが生成分布を変える」というなら、それはテスト駆動開発でやっていたことではないかと思う。
      主な違いは、生成関数が LLM ではなく人間だったという点だけだ。途中の人間を抜いてはいけない理由はなさそうだ。
    • 指示チューニング自体は「些細な」ほうで、本当に難しいのは境界条件を扱う部分だ。
      従来のコードでは境界条件は文字どおり小さな特殊ケースに近かったが、LLM では何が妙な方向に飛ばすのか分からず、パース用コードはその混沌に対処しなければならない。
      言い換えると、境界条件として扱うべきケースの割合が劇的に上がったように感じる。
  • GPT-4 は、システムメッセージに例を入れるだけで有効な JSONを返すようにでき、10回中9回は動作する。
    しかし依然として確率的で、10回中9回では十分ではない。
    ときどき {"key1": "value1", "key2": "value2" for i in range(n)} のような幻覚応答を作ることもある。
    パースエラーのメッセージと一緒に再プロンプトすると、たいてい2回目の試行では解決する。
    ただし、ダブルクォートと改行文字をエスケープするのはあまり安定しない。複数の例を与えても半分程度しか正しくエスケープせず、エスケープエラーを再プロンプトしても成功率は約50%にとどまる。

    • エラーが出たときに再プロンプトする方式は、Microsoft の新しいライブラリ TypeChat でも使われている: https://github.com/microsoft/TypeChat
      そのプロンプトはここにある: https://github.com/microsoft/TypeChat/blob/c45460f4030938da3...
      ここで見られる文法ベースのアプローチや https://github.com/ggerganov/llama.cpp/pull/1773 のような方法のほうが、はるかに優雅な解決策だと思う。
    • JSON よりも XML 出力をさせるほうがうまくいった。(1) XML には実際の言語や意味をより多く含められ、LLM はそういうものを好むし、(2) パーサーをより寛容にできる。
      人々が JSON を欲しがるのは理解できるが、私には猫を泳がせようとしているように見える。最終的には成功するかもしれないが、自然な性向ではない。
    • ChatGPT の関数呼び出しを使うと、プロンプトでミスしない限り、GPT-4 から有効な JSON を 100% 得られている。
      主なミスは逃げ道を用意しないことだ。LLM は正しい答えを見つけようとするので、テキスト群を入れて構造化データを返すように言ったとき、あるテキストが空だと正しい答えを決めにくくなり、幻覚が生じる。
      解決策は、引数の1つに textIsMissing のようなブール値を置く形で逃げ道を作ることだ。こうした失敗パターンを考慮しておけば、問題なく動作する。
    • GPT-4 に、任意の JSON の中にサンプルの PHP コードを入れて返すよう頼んだところ、最初の試行から JSON リンターを通らなかった。
      何度も再試行し、追加の修正までさせたが検証を通過せず、100% 有効な JSON を一度も生成できなかったので、結局あきらめた。
    • 文法制約付き生成には大きな利点が2つある。
      第一に、プロンプトにあまり多くの例を入れる必要がないので、トークン消費が少ない。
      第二に、忘却問題の影響を受けにくい。
      小さな利点としては、望む出力をどこから開始すべきか正確に制御できる。ただし全体としては、よい追加機能ではあるが、非常に本質的なものだとは見ていない。
  • LLMの力の大きな部分は、応答の較正された確率分布にありますが、この手法はおそらくその能力を捨てているように見えます。なぜこれで十分なのか疑問です。
    簡単な例として、LLMの取り得る出力が「hello world」「food」「hello」「good day」だけで、プロンプトがないときはいずれも同じ確率だとしましょう。文法は、出力のどこかに空白がなければならない、という制約だけを課すと仮定します。
    文法を通過するまでLLMの出力をサンプリングすれば、「hello world」と「good day」を同じ確率で受け取ることになります。しかし、このウェブサイトの手法を適用すると、「hello world」は「good day」より2倍多く出てきます。
    核心的な問題は、ある回答プレフィックスが有効な応答につながる可能性は極めて低かったかもしれないのに、この手法は成功すると仮定したうえで、そのプレフィックスから有効な応答を作り出してしまう点です。適切な箇所で十分に独立していれば問題ないかもしれませんが、自己回帰モデルでは相関した誤りが急速に蓄積します。
    JSONに限って問うなら、スキーマを守れない応答を作るとき、LLMは事実誤り、ハルシネーション、途中で切れた文字列、主要人物の欠落などをより多く作るのでしょうか、それとも少なく作るのでしょうか。事実誤り率がスキーマ誤り率と非自明に関連しているなら、この道は見た目より危険です。特定の単語やくっついてしまった語句がLLMの出力に大きな影響を与えることを考えると、スキーマ準拠のような細部も出力の他の特性へ波及する可能性は高そうです。

    • この場合のような多肢選択生成なら、可能な出力のうち正規表現に合わないものを単に生成から除外すればよいです。
      「回答プレフィックスが有効な応答につながる可能性は非常に低かったが、手法が何とか有効な応答を組み立てて問題を起こす」例を考えようとしましたが、うまく思い浮かびません。良い例があれば、興味深い研究課題になり得ます。
  • 関連して、LLama.cppが先月、文法ベースのサンプリングを実装しました。
    https://news.ycombinator.com/item?id=36819906
    https://github.com/ggerganov/llama.cpp/pull/1773

    • 上記の論文で説明したように、私たちのアプローチも文法ベースのサンプリングへ拡張できます。関連PR: https://github.com/normal-computing/outlines/pull/178
      私たちの方式のほうがはるかに効率的です。llama.cppは各ステップでマスクを作るために、約5万トークンの語彙全体を走査します。
      私たちは初期化時にインデックスを作り、各ステップでマスクを構成するときは辞書ルックアップを行うだけです。速度をメモリと引き換えにする方式で、サンプリングは標準的なサンプリングと同じ速さです。
    • 同じような時期に、私たちも文法駆動の誘導の実装を持っていました: https://github.com/normal-computing/outlines/pull/131
      関連論文が多かったことを考えると、他にも複数あったはずです。ここおよび現在の作業の要点は、非常に低コストな誘導を提供することで、正規表現のケースではしばらく前から実装されており、その後JSONへ拡張しました。
  • こういうものを作ってくれてありがたいですし、動作原理はあまりに当然のアイデアなので、ファーストパーティのプラットフォームがまだやっていないことに驚きます。
    JSON以外にも、構造化入力が必要な他のタスクにどう使えるのか気になります。

    • LLM技術は今、非常に速い軍拡競争の状態にあり、数か月ごとに劇的に変わっていると理解していました。
      開発者リソースが限られている結果かもしれません。10年ものの技術にこうした基本機能が欠けていたら驚きますが、軍拡競争中のAI技術でまだ便利機能が欠けているのは納得できるように思えます。
    • このアプローチを文法ベースのサンプリングに拡張し、上にリンクされた論文で説明しています。関連PRは https://github.com/normal-computing/outlines/pull/178 です。
    • https://arxiv.org/abs/2306.10763 の「Guiding Language Models of Code with Global Context using Monitors」は、言語モデルにハルシネーションによる逆参照なしでコードを生成させる方法を示しています。
  • これが次のプロジェクト群とどう違うのか、よく分かりません。
    https://github.com/1rgs/jsonformer
    https://github.com/newhouseb/clownfish
    https://github.com/mkuchnik/relm
    https://github.com/ggerganov/llama.cpp/pull/1773
    https://github.com/Shopify/torch-grammar
    全般に、こうしたロジットベースの誘導システムは非常に多くありますが、大きな支持を得ていない理由は、最高性能のモデルがこのような細かなアプローチを許さないREST APIの背後にあるからです。
    それらのモデルははるかに性能が良いので、人々はたいてい正しい形式が出るまで再リクエストする方式で満足しています。GPT-4では、私の経験上そうした失敗もかなりまれです。

    • clownfishとrelmを教えてくれてありがとうございます。私の知る限り、他のライブラリは生成の各ステップごとに語彙全体を走査します。
      私たちは初期化時に語彙を一度走査してインデックスを作り、その後の生成は標準的な生成と同じ速さです。
  • 別の言い方をすると、LLM がトークンを1つ生成するたびに、次のトークンが有効な JSON トークンだけになるようロジットバイアスの「マスク」を更新している、ということ? すごくクール

    • 文字列全体が有効になるまで生成し続ける必要があるはずだけど、もしループにはまったらどうなるのか分からない
      この方式がどうやって本当に 100% を保証できるのか確信が持てない
    • その通り。そして語彙全体を走査せずに、辞書引きでマスクを更新できる。全走査は遅い
    • JSON トークンはロジットと正確に対応していないので、何らかの形のビームサーチか棄却サンプリングも必要になる
      追記: 論文ではこの部分をより慎重に説明している
    • 実際にはかなり昔からある手法で、多くのライブラリがこれをやっている。このライブラリの何がそんなにすごいのか、よく分からない
  • この Brandon Willard は、Detroit のブレイクダンサー Brandon Willard なのか?
    追記: そうだった! https://brandonwillard.github.io/

    • そう。かなり昔のことだけど、本当に楽しかった
  • remilouf は、自分のバックグラウンドが「確率的、関係的、記号的プログラミング」だと言っているので、正規文法や文脈自由文法、実質的にどんなレベルの文法でもテキストを生成すること自体は問題ではないと理解しているのだと思う
    例えば関係型言語である Prolog では、Definite Clause Grammars 記法で文法さえ与えられれば、非常に簡単にできる
    私には、このアプローチではユーザーが文法を提供する必要があるように見える。だとすると、テキスト生成にLLMを使う利点は何なのか気になる
    単に文法を生成器として実行して、欲しいテキストを作ればよいのではないだろうか? そうすれば、そもそも LLM を訓練する大きな手間とコストを減らせる。構造化テキストだけを生成したいなら、自然言語モデルである LLM がなぜ必要なのかも疑問だ

    • それだと、完全にランダムだが有効な出力が出てくるのでは? ここで必要なのは、リクエストに関連した有効な出力
      構造化テキストだけが欲しいとしても LLM が必要な理由は、人間が書いた非構造化テキストをパースして、機械が使える構造化データを返すためだ
    • 目標は、文法に合う任意のランダム文字列を生成することではない
      リクエストが「円周率の最初の10桁は?」で、応答を正規表現 "[0-9]+\.[0-9]+" に制限したなら、単にパターンに合う「1.2346789」のようなランダム文字列ではなく、実際の正解である 3.1415926535 を受け取ることが目標だ
    • IanCal が全部言ってくれた。ただし miniKanren と一緒に LLM を使う代替的なアプローチは https://arxiv.org/abs/1809.02840 で見られる
  • 興味深いし、最近われわれも llama の出力が TypeScript インターフェースに合うよう制限する似たツールを作った[1]
    出力形式の保証は、LLM が実際におもちゃではない用途で使われる今後数十年にわたって重要になると強く信じている
    [1] https://github.com/ggerganov/llama.cpp/discussions/2494