小さなLLMをQLoRAでファインチューニングして、Function Callingを覚えさせてみた
小さなオープンモデルは、ツール呼び出し(function calling)が苦手なことが多いです。「何をすべきか」は理解して正しい関数も選ぶのに、出力するJSONの構造が微妙に間違っていて、そのままではパーサーが実行できない——ということが起きます。
この記事では、Llama-3.2-1B-Instruct をQLoRAでファインチューニングして、その「構造のズレ」を直した記録をまとめます。すべて Kaggle の無料T4 GPU で動かしました。課金なしです。
学習用というより、自分の理解のために手を動かしてみた小さなプロジェクトです。
何が問題だったか
学習前のベースモデルに、標準的なツール定義(get_weather)を渡して名古屋の天気を聞いてみると、こう返ってきました。
{"type": "function", "function": "get_weather", "parameters": {"city": "Nagoya", "unit": "celsius"}}
意図は合っています(関数も都市も正しい)。でも構造が2か所おかしい。
-
"function"が文字列になっている。本来はここにnameとargumentsを持つオブジェクトが入るべきなのに、平坦になっている。 -
"parameters"を使っている。ツールを「定義」するときはparametersですが、実際に「呼び出す」ときの引数はargumentsに入れるのが正しい。モデルはこの2つを混同していました。
面白かったのは、2つ目のエラーの原因です。Llama のチャットテンプレート自体が「parameters というキーで返せ」とモデルに指示していたのです。一方で最近の慣習(そして学習データ)は arguments を使う。つまりベースモデルはデタラメを出していたのではなく、テンプレートの古い指示に忠実に従っていただけでした。こういう「作り込まれた挙動」を上書きできるのが、ファインチューニングの効くところです。
結果
ファインチューニング後、同じプロンプトでこうなりました。
[{"name": "get_weather", "arguments": {"city": "Nagoya", "unit": "celsius"}}]
2つのエラーが両方とも直っています。name と arguments が正しくなり、学習データが使っているリスト形式で包まれるようになりました。試した他のプロンプトでも同じ形式が安定して出ました。
やったこと
ベースモデル: meta-llama/Llama-3.2-1B-Instruct
あえて一番小さい1Bを選びました。ツール呼び出しがもともと苦手なので、before/after の差がはっきり出るからです。強いモデルだと最初からだいたい正しく出せてしまい、見せ場がありません。
データセット: Salesforce/xlam-function-calling-60k
各サンプルが「ユーザーの質問・使えるツール・正しい呼び出し」の3点セットになっています。今回は500件だけ使いました。少ないですが、出力フォーマットを覚えさせるだけならこれで十分でした。
手法: QLoRA。2つの組み合わせです。
- 量子化 — ベースモデルを4bitで読み込み、メモリ使用量を約1/4に。これで無料のT4に載ります。
- LoRA — 12億パラメータ全部を学習するのではなく、ベースは凍結して、小さなアダプター(約1100万パラメータ、全体の0.9%)だけを学習する。
全体ではなくアダプターだけを学習するので、無料GPUで動き、成果物も約40MBと小さく済みます。
学習設定: 2エポック、実効バッチサイズ16、学習率2e-4、rank 16。最終的な mean token accuracy は約88%でした。
つまずいたところ(正直な記録)
-
bitsandbytesが import できない — インストール後にカーネルを再起動しないと反映されませんでした。コンパイル済みライブラリではよくあるやつです。 -
学習直後の生成が文字化けした — モデルが学習モードのままだったのが原因。
model.eval()で推論モードに切り替えたら直りました。 - セッションリセットでアダプターが消えかけた — Kaggle の作業ディレクトリはセッションごとにリセットされます。保存先のパスを勘違いしていて焦りましたが、保存したバージョンの output に残っていて助かりました。学習したものはすぐ Hugging Face に push するのが安全です。
限界
500件・2エポックで「出力フォーマットを直す」ことに絞ったので、幅広いツールに対応できる汎用モデルではありません。構造の正しさは確認しましたが、難しい・曖昧な質問で正しいツールを選べるかまでは評価していません。同じ手順を3Bや7Bのベースに適用すれば、実用的なモデルになるはずです。
まとめ
Function Calling は、モデルが「話す」だけでなく「実際に何かをする」ための仕組みです。小さいモデルやローカルモデルを使うチームにとって、「意図は理解しているのに、実行できる形式の呼び出しを出せない」というギャップは地味に効く問題で、そこは小さくて安いファインチューニングで埋められます。かかったのは無料GPUの時間だけでした。
学習したアダプター: Thanush16/llama-3.2-1b-function-calling