Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

CLM(Contrastive Language Model)

CLM は、対照学習(contrastive learning) で「状態(state)」と「行動(action)」を結びつけるSystem One model。

Stanford の Jacky Kwok らが Hazy Research などと共同で開発し、2026年9月23日に CLM-8B(CLM-v0.1-8B) として発表した。Jevと同程度の性能で 最大9倍速い 推論を実現したと報告されている。

アイデア

System One modelが行う「判断」を、stateに対して最も合うaction(選択肢)を検索する問題 とみなす。

CLIPが画像とテキストを同じ埋め込み空間に写したように、CLMは stateとactionを別々のエンコーダーで同じ埋め込み空間に写し、その類似度で選択肢のスコアを決める。

  • state:判断の前提となる情報(会話・画面の状態・エージェントの履歴など)

  • action:選択肢(ツール名・次の操作・回答候補など)

アーキテクチャ

  • バックボーン:凍結した Qwen3-8B(エンコーダーとして使う)

  • その上に、学習可能な小さな 射影ヘッド(約20Mパラメータ)が2つ

    • state head

    • action head

  • stateとactionは 分離(disaggregated) されてエンコードされ、埋め込みの内積(類似度)でスコアを出す

stateの埋め込みを s=fs(state)\boldsymbol{s} = f_s(\text{state})、actionの埋め込みを ak=fa(actionk)\boldsymbol{a}_k = f_a(\text{action}_k) とすると、選択肢の確率は

p(ak∣s)=exp⁡(s⊤ak/τ)∑jexp⁡(s⊤aj/τ)p(a_k \mid s) = \frac{\exp(\boldsymbol{s}^\top \boldsymbol{a}_k / \tau)}{\sum_j \exp(\boldsymbol{s}^\top \boldsymbol{a}_j / \tau)}

のように候補間のsoftmaxで得られる(τ\tau は温度)。

学習

損失関数:双方向 InfoNCE

バッチ内の正例ペア (si,ai)(s_i, a_i) について、state→action と action→state の 両方向 で検索がうまくいくように最適化する。

L=−12N∑i=1N[log⁡exp⁡(si⊤ai/τ)∑jexp⁡(si⊤aj/τ)+log⁡exp⁡(si⊤ai/τ)∑jexp⁡(sj⊤ai/τ)]\mathcal{L} = -\frac{1}{2N} \sum_{i=1}^N \left[ \log \frac{\exp(\boldsymbol{s}_i^\top \boldsymbol{a}_i / \tau)}{\sum_j \exp(\boldsymbol{s}_i^\top \boldsymbol{a}_j / \tau)} + \log \frac{\exp(\boldsymbol{s}_i^\top \boldsymbol{a}_i / \tau)}{\sum_j \exp(\boldsymbol{s}_j^\top \boldsymbol{a}_i / \tau)} \right]

(CLIPと同じ形の対称な損失)

3段階の学習

段階データ目的
事前学習(pre-training)Nemotronの質問・回答ペア 約6000万組広い意味理解
中間学習(mid-training)Gemini 2.5で合成した ハードネガティブ(誤答)約3000万件細かい違いの識別
事後学習(post-training)エージェントの作業履歴 約100万件(ADP、Endless-Terminals、LiteCoderなど)エージェント的なタスクへの適応

事後学習では、破滅的忘却を防ぐために Nemotron のデータを40%リプレイし、残り60%をエージェントのトレースにしている。

バックボーンは凍結し、射影ヘッドだけを更新するので学習が軽い。

なぜ速いのか:埋め込みのキャッシュ

stateとactionを分離してエンコードするため、それぞれの埋め込みを独立にキャッシュして再利用できる。

  • ツールの一覧やゲームの操作など、同じ選択肢が何度も出てくる 環境では、actionの埋め込みを1回計算すれば使い回せる

  • 同じstateを再訪するときはstateの埋め込みも再利用できる(RTX 4090で3択の場合 1.7ms → 0.6ms、2.8倍速)

Jevの場合、再利用できるのはstate側の計算結果だけとされており、選択肢が多いほどCLMが有利になる(候補が多いと最大13倍という報告もある)。

これは情報検索でいう bi-encoder(CLM)と cross-encoder(stateと選択肢を一緒に入れるモデル)の関係と同じで、bi-encoderは速いがstateと選択肢の細かい相互作用は捉えにくい、というトレードオフがある。

性能

タスク結果
コンピューター操作・ゲーム・ツール呼び出し(ゼロショット)Jevと同等、最大9倍速い
DeepSWE(verifierとして。軽いfine-tune後)81.6%(SOTA)
Terminal-Bench 2.1(verifierとして。軽いfine-tune後)87.6%(SOTA)

コーディング課題では、複数の解答候補から正解を選ぶ verifier として使われている。gihyoの記事によれば、この設定でJevは無作為選択を下回った一方、CLMは正解を選別できたとのこと。

使い方

TypeSafe互換のAPI(POST /v1/systemone)でCLM-8Bをサーブできる。埋め込みの計算にはvLLMを使う。

pip install contrastive-lm

# Qwen3-8B エンコーダーを GPU で起動(pooling runner)
vllm serve Qwen/Qwen3-8B --served-model-name qwen3-8b --runner pooling --port 8090 &

# CLM API を :8700 で起動
clm-serve
from clm import CLMClient, Choice, Noul, Score

client = CLMClient()
r = client.system_one(
    state="Customer: my invoice was charged twice!",
    questions={
        "urgency": Noul(instructions="Is this urgent?"),
        "department": Choice(
            instructions="Which team?",
            criteria={
                "billing": "Charges, invoices, refunds",
                "technical": "Bugs and outages",
            },
        ),
        "frustration": Score(
            instructions="Customer frustration level?",
            criteria=["Calm", "Frustrated", "Very angry"],
        ),
    },
)
print(r.answers["department"].choice)  # "billing"
print(r.answers["frustration"].score)  # 0..2 のレベルの期待値

サーバーを立てずに、プロセス内で候補をランキングすることもできる。

from clm import Engine

engine = Engine(emb_url="http://127.0.0.1:8090/v1/embeddings")
engine.rank(
    "What causes tides?",
    ["The Moon's gravitational pull.", "Photosynthesis.", "Earth is round."],
)
  • POST /v1/rank:文脈+質問+候補を渡すと確率順に並べる(基本の操作)

  • GET /:Webのプレイグラウンド

  • GET /v1/models:利用できるヘッドの一覧

限界

  • 文章生成はできない(System One modelなので当然)

  • 返す確率は 候補の中での相対値 であり、候補の組み合わせを変えると確率も変わる(Jevのような絶対的なキャリブレーションとは性質が違う)

  • stateは2048トークンで切り捨てられる(設定で延ばせるが、GPUメモリが必要)

  • 外部のvLLM埋め込みサーバーが必要

  • キャッシュはヘッドの重みに紐づくので、重みを差し替えるとキャッシュは無効になる

  • 画像・動画は未対応(マルチモーダル版の CLM-35B を予定)

重みは Apache 2.0 ライセンスで公開されている。