太陽がまぶしかったから

C'etait a cause du soleil.

Function Calling 対応の Qwen3-30B-A3B を VSCode Extension から自動操作してローカル完結の軽量LLMコードレビュー環境を構築

Qwen3-30B-A3B の衝撃

Alibaba 製の Mixture-of-Experts(MoE)LLM モデルである Qwen3 が 2025 年 4 月 28 日にリリースされ、体感 ChatGPT 4 ぐらいの性能のモデルがローカルで動かせ、Apache 2.0 ライセンスで利用可能とのことで、ゴールデンウィーク中に遊んでいた。

「Qwen3-235B-A22B」は、「DeepSeek-R1」「o1」「o3-mini」「Grok-3」「Gemini-2.5-Pro」などの他のトップティアモデルと比較して、コーディング、数学、一般的な機能などのベンチマーク評価で競争力のある結果を達成しています。さらに、小型の MoE である「Qwen3-30B-A3B」は、10 倍のアクティブパラメータを持つ「QwQ-32B」を凌駕し、「Qwen3-4B」のような小さなモデルでさえ、「Qwen2.5-72B-Instruct」の性能に匹敵します。

モデル自体の詳細は上記 note にまとまっているため、参照いただくとして、特に面白いのが MoE という仕組みだ。Qwen3-30B は、128 のエキスパートから 8 つだけを推論時に選び出して使う「部分的活性」な Mixture-of-Experts 構造を持つ。つまりモデル全体は 30B でも、実際に動くのは 3B 程度であるため、処理コストを大幅に削減しながら、高度な自然言語処理を実現している。

また 30B= 300 億 のパラメータを FP16(2 バイト)の浮動小数点で使えば 60GB 必要になるが、4bit 量子化することで 15GB、これにメタデータや推論メモリなどを加えて 20GB 程度の GPU メモリで起動できる。Mac においては CPU メモリと GPU メモリを共有するユニファイドメモリアーキテクチャが使えるため、メモリ 32GB 以上のちょっと良い MacBook ProMac Mini であれば現実的な処理負荷と処理速度でそれなりの性能をもつ LLM がローカル完結で動かせるのだ。

その上で後述する、/no-think モードと、Function Calling により十分に実用かつ構造化された処理結果を低負荷で実行させ続けることができるようになったことで、ブレイクスルーの気配をヒシヒシと感じることができたので共有したい。

軽量だけどもインテリジェントな lint が欲しい

ローカル LLM についてはちょくちょく試していたものの、コマンドラインで動かすだけではいまいち活かせている感じがなかった。しかしながら、コード生成エージェントとの連携などを試していくうち、ローカル完結でインテリジェントに潜在的な問題点を指摘してくれる lint のようなものを作ってみたらどうかと思い至った。

もちろん、コード生成エージェントにもコードレビューをしてもらえるが、コードを保存するたびに実施されたらリズム感が損なわれてしまうし、API 利用費も気になる。あくまで静的解析と同じ頻度と負荷で自動的なバックグランド処理でコードレビューをしてもらうことに価値がある。

Git の設計思想としても飛行機などによる一時的なオフライン環境や常時接続が難しい国にオフショア開発を依頼するようなユースケースにも対応できるようリモートプッシュをするまではローカル完結で動く仕組みとなっているが、そもそも SVN のように都度通信するのは遅いしダルいといった怒りへの反動もあったと想像する。つまり、コードを書いているうちはオフライン・API レス・無料(≒ 電気代)で高頻度に実施し、ある程度まとまったらリモートにいく発想自体は普遍的なものだ。

LM Studio のインストールと CUI 起動

ローカル LLM を起動する方法として、今回は LM Studio を利用する。LM Studio はローカルで大規模言語モデル(LLM)を GUI から起動・管理・利用可能にするマルチプラットフォームツールである。

上記からダウンロードしてインストールすることができる。GUI として起動すると Chat GPT などでお馴染みのインターフェイスとモデルの検索・ダウンロード・ロードの管理まで一体化していて非常に便利だ。

Mac であれば「探索」で、 "Qwen3-30B-A3B-MLX-4bit" を探してダウンロード。Windows であれば "Qwen3-30B-A3B-GGUF" が良いだろう。モデル名の意味としては Qwen3 のうち 300 億のパラメータに絞り、Moe で 30 億パラメータをアクティブ化。MLX(Machine Learning eXperimental)という Apple が開発している macOS/iOS 専用の機械学習フレームワークに対応しており、パラメータを 4bit 量子化していると読み解ける。

LM Studio は GUI ツールとして ChatGPT がわりに使えるだけでなく、Open AI 互換のエンドポイントをもったローカルサーバーとして起動できる機能がある。GUI を実行してモデルをロードすると「開発者」のメニューからロードしているモデルと URL を確認することができる。

# curl http://localhost:1234/v1/chat/completions -H "Content-Type: application/json" -d '{ "model": "qwen3-30b-a3b-mlx", "messages": [{ "role": "user", "content": "こんにちは!" }]}'

Open AI 系の API で開発したことがある人ならお馴染みのリクエストを投げればちゃんと返答が戻ってくる。現状の AI 開発シーンでは Open AI の APIデファクトになっているため、学習コストやスイッチングコストの観点から独自 API だけでなく互換の API を提供する流れがローカル LLM に限らず出てきてありがたい。GUI 起動からもサーバーとして使うこともできるが、バックグラウンドで動かすことを前提にする場合には CUI のサーバーとして起動することもできる。

# lms load qwen3-30b-a3b-mlx
# lms server start
# lms log stream &

こんな感じで、lms コマンドを実行していくことでサーバー起動してログも順次出すことができる。

起動したローカル LLM サーバーを Cline から利用する設定も簡単だ。LM Studio 側の「マイモデル」メニューの歯車アイコンから最大コンテキストサイズの設定も調整してやる必要がある。インプットログを出力させることでどんなプロンプトエンジニアリングが行われているかを丸裸にすることができるが、改めて Cline は大量のコンテキストウィンドウを連続で取り扱っており、処理負荷や出力内容的にローカルで有用に取り扱うのは現実的でないことも教えてくれる。動くと感動的ではあるけれども。

ローカル LLM を VS Code Extension から動かす

そんなわけで、Cline でどうこうするのは諦めたが、そもそも作りたかったのは軽量なインテリジェント lint だ。これを実現する前段階として VS Code Extension の知識が必要になるが、難しいところは yo code が自動生成してくれる。そんで、足りない部分を VIBE Coding すれば OK だ。

next.js などもそうだが、コード生成エージェントを動かす前に枯れた雛形生成ツールによって動作確認できる状況までやってもらうのがまず大事で、コード生成エージェントから変なものを生成して苦労するより、この手のツールをダウンロードして実行するのが本来の AI エージェントの役割だろと思ったりもする。

この辺りが実装の本体。qwen3-30b-a3b-mlx は Tools 形式の Function Calling が安定しして動くため、コードレビューを依頼して JSON 形式の引数で返却させる処理が簡単に書ける。当初は文字列解析系の処理をゴリゴリ書いたり、構造化出力を試したりだったが、エージェント用途への対応のために Function Calling 機能が強化されているらしいという情報で一気にブレークスルーが起きた。このあたりは gpt-3.5-turbo 時代の感動のループをしていて味わい深い。

JSON 出力できてしまえば、あとは問題タブに反映させるコードを書けば OK。とはいえ、指摘が的外れなこともあって、本家の lint のように「直すべきもの」と同じところに置くとノイズになる可能性もあるため、別タブに切り分けるオプションも作った。ナレッジカットオフが故の指摘も鬱陶しいので、ルール設定や API 情報の更新の仕組みな改良の余地もまだまだあるが、とりあえず動くものはできている。

LLM が苦手な行数や文字位置のカウント問題への対応

とりあえず、動くものはできたが、指摘箇所の行や文字のカウントが苦手でうまくコード上の位置を指摘できないという問題が残った。これは、qwen3-30b-a3b-mlx に限らず、ChatGPT 4o ですらよく起こるのだけど LLM のわかりやすい弱点なようなものだ。この問題に対応するため以下の手順で位置を特定して結果に付与している。

  • LLM には function calling で問題箇所のコードスニペットを引数として出力
  • TypeScript 側でコードスニペットを文字列検索
  • 出力結果として上記で取得した行番号と文字番号をデコレートした文字列を生成

上記のようにすることで、適切な指摘箇所に波線を書いたり、コード移動もスムーズに行える。この辺りも今のところは基本的なエンジニアリングとのハブリッドな処理が必要な場面だと思うが、LLM モデルそのものの話ではないのが難しいところ。ChatGPT でいうところの、コードインタープリターのようなものを汎用的に噛ませる基礎 API レイヤーも欲しいと思っている。

処理負荷問題については、連続起動を抑制する仕組みを入れつつ、/no-think をプロンプトで Non-Thinking Mode もを指定している。これは通常の Thinking Mode モードよりも長考させないモードで、低負荷かつ高速に返答できる。同じモデル内で用途に応じてモードを明示的に切り分けられるのも便利だ。一定以上の修正があったら /think にするなども考えられる。

ローカル LLM は使いようによってはすごく便利

正直なところで、 gpt-4.1-mini などの高速、激安、安定なモデルが生まれていく中でのローカル LLM の立ち位置は微妙なところにあったが、そもそもが高品質で高効率なモデルに対して、MoE や思考モードの切り替えや量子化などを行っていくことでローカル環境でも安定して連打できうる LLM が生まれたのは大きい。

なんといっても Function Calling である。これができることで、LLM に意図を構造的に伝えられるようになり、出力結果と出力形式をある程度は保証できるようになる。目の前の MacBook PRO で現実に動いてしまっているローカルで早くて無料でそこそこ精度の高いモデルをどのように活かしていけるかを改めて考えていきたい。ちょっと時代の転換点にいる気がするぞ。