Chain-of-Thought Decoding

CoT Decoding в 2026: inference-time search trick, где reasoning-path извлекается через альтернативные варианты декодирования, а не через wording промпта.

Chain-of-Thought Decoding (CoT-Decoding) — метод, позволяющий извлечь reasoning path из модели без специального prompting. В 2026 его полезно понимать как inference-time search trick: вы меняете не формулировку запроса, а сам способ декодирования, чтобы найти путь генерации, который запускает более качественное рассуждение.

Представьте, что вы спрашиваете у эрудита: «Какая столица Австралии?» Он мгновенно отвечает: «Сидней». Но если попросить его чуть задуматься, он скажет: «Подожди... Сидней — крупнейший город, но столица — Канберра». Ответ всегда был у него в голове — просто первая реакция «на автомате» вела не по тому пути. CoT-Decoding делает то же самое с моделью: вместо первого «автоматического» слова проверяет альтернативные начала ответа — и находит те, что запускают правильное рассуждение.

Суть в двух словах

Когда языковая модель генерирует текст, она на каждом шаге выбирает наиболее вероятный токен — это называется greedy decoding (жадное декодирование). Проблема в том, что самый вероятный первый токен часто ведёт к короткому, поверхностному ответу без рассуждений.

CoT-Decoding предлагает простую идею: посмотреть на альтернативные варианты первого токена (top-k). Оказывается, среди них часто находятся токены, которые запускают полноценную цепочку рассуждений — и приводят к правильному ответу.

Модель уже «знает», как рассуждать. Просто greedy decoding выбирает не тот путь.

Greedy decoding (стандартный)
Вопрос: Если у Джона 3 яблока, он купил ещё 5, отдал 2, сколько осталось? Модель сразу выбирает первый токен: «6» Ответ: 6 Один токен, никакого рассуждения. Greedy-путь — самый вероятный, но поверхностный.
CoT-Decoding (top-k альтернативы)
Вопрос: Если у Джона 3 яблока, он купил ещё 5, отдал 2, сколько осталось? Модель рассматривает top-k первых токенов: k=0: «6» → 6 (без рассуждения) k=1: «У» → «У Джона было 3, +5=8, -2=6. Ответ: 6» k=2: «Дав» → «Давайте посчитаем: 3+5=8, 8-2=6» Выбираем путь с наивысшей уверенностью → 6 (с рассуждением)

Ключевая идея: CoT-промптинг заставляет модель рассуждать «извне» — через формулировку промпта. CoT-Decoding раскрывает рассуждения, которые уже заложены «внутри» модели, — через анализ альтернативных путей генерации.

CoT-Decoding доказывает, что способность к рассуждению — это внутреннее свойство предобученных моделей, а не навык, привнесённый промптингом. Модель всегда «умела» рассуждать — greedy decoding просто не давал ей этого сделать.

Как понимать CoT-Decoding в 2026

С практической точки зрения это важно не потому, что всем срочно нужен top-k search на первом токене, а потому, что техника сдвигает фокус:

  • reasoning зависит не только от prompt;
  • decoding policy тоже может менять качество;
  • "умность" модели частично скрыта в альтернативных путях генерации.

Это делает CoT-Decoding ближе к runtime/inference engineering, чем к обычному prompt engineering.

Как работает CoT-Decoding

Проблема greedy decoding

Стандартная генерация текста работает так: на каждом шаге модель вычисляет вероятности всех возможных следующих токенов и выбирает самый вероятный. Это называется greedy decoding — жадный алгоритм.

Проблема: для многих задач самый вероятный первый токен — это сразу ответ (число, «Да», «Нет»), без какого-либо рассуждения. Модель «знает» правильный формат: вопрос → короткий ответ. Но этот путь часто ведёт к ошибкам на задачах, требующих логики.

Что открыли исследователи

Wang и Zhou из Google DeepMind обнаружили закономерность: если вместо самого вероятного первого токена взять альтернативные варианты (2-й, 3-й, 5-й по вероятности), многие из них естественно запускают цепочку рассуждений.

Например, для математической задачи:

  • Top-1 токен: «8» → модель сразу выдаёт число (часто неверное)
  • Top-3 токен: «Давайте» → модель продолжает: «Давайте разберёмся шаг за шагом...» и приходит к правильному ответу
  • Top-5 токен: «Сначала» → модель рассуждает: «Сначала найдём...»

Модель уже обучена на текстах с рассуждениями, поэтому альтернативные пути декодирования часто содержат CoT-паттерны.

Механизм работы

Сравнение с CoT-промптингом

CoT через промптинг
Промпт: «Думай шаг за шагом. Задача: ...» Модель: генерирует рассуждения по инструкции. + Работает с любой моделью через API + Не требует доступа к logprobs - Удлиняет промпт, тратит входные токены - Качество зависит от формулировки - Требует prompt engineering
CoT через декодирование
Промпт: «Задача: ...» (никаких инструкций) Модель: исследуются top-k первых токенов, находятся пути с рассуждениями. + Не нужен специальный промпт + Рассуждения — собственные, не навязанные - Требует доступа к logprobs / top-k - Вычислительно дороже (k генераций) - Не всегда доступно через API

Метрика уверенности (confidence)

Главная находка авторов — метрика для автоматического выбора лучшего пути. Для каждого сгенерированного пути вычисляется:

Decoding confidence = разница (дельта) между логарифмической вероятностью топ-1 токена ответа и топ-2 токена ответа на финальной позиции.

Интуиция: если модель после рассуждений очень уверена в одном конкретном ответе (большой разрыв с альтернативами), значит, рассуждения были полезными и ответ, скорее всего, правильный.

Результаты

Точность на GSM8K (Mistral-7B) — без CoT-промпта
Greedy decoding29%
CoT-Decoding (k=5)48%
CoT-Decoding (k=10)63%
CoT prompting52%

Ключевые выводы из статьи:

  • На Mistral-7B CoT-Decoding (k=10) превосходит стандартный CoT-промптинг на GSM8K: 63.2% vs 52.0%
  • На PaLM-2 Large: CoT-Decoding даёт +14 п.п. на MultiArith и +7 п.п. на AQuA
  • Улучшения наблюдаются на всех протестированных моделях — от 7B до 540B параметров

Когда использовать CoT-Decoding

Плюсы

  • Не требует prompt engineering — рассуждения извлекаются из модели как есть
  • Может превосходить CoT-промптинг — модель использует собственные, а не навязанные паттерны рассуждений
  • Доказывает наличие скрытых способностей — помогает понять, что модель реально знает
  • Применимо к любой задаче — не нужно подбирать формулировку промпта

Минусы

  • Требует k генераций вместо одной — вычислительные затраты растут линейно
  • Нужен доступ к logprobs — не все API его предоставляют
  • Не работает через стандартный чат-интерфейс — нужен программный доступ
  • Latency: время ответа увеличивается в k раз без параллелизации
CoT-Decoding — исследовательский метод, а не продуктовая техника. На практике он требует доступа к logprobs и top-k токенам через API, а также k-кратного увеличения вычислений. Для большинства задач стандартный CoT-промптинг проще и дешевле. CoT-Decoding особенно полезен для исследования возможностей модели и для задач, где prompt engineering не даёт результатов.

Где метод реалистичен, а где нет

CoT-Decoding выглядит реалистично там, где у вас есть:

  • доступ к logits или top-k decoding internals;
  • control над inference stack;
  • оффлайн-эксперименты над open-weight моделями;
  • готовность платить extra compute ради accuracy uplift.

Он плохо ложится на обычные hosted API workflows, где:

  • нет доступа к нужным decoding internals;
  • latency already tight;
  • проще сделать prompt routing или self-consistency, чем строить custom decoding policy.

Техническая реализация

Базовая реализация CoT-Decoding

CoT-Decoding можно реализовать через API, поддерживающие logprobs. OpenAI API возвращает top_logprobs — вероятности альтернативных токенов на каждой позиции.

from openai import OpenAI
import math

client = OpenAI()


def cot_decoding(
    question: str,
    k: int = 10,
    model: str = "gpt-4o",
    max_tokens: int = 512,
) -> dict:
    """
    CoT-Decoding: генерация k альтернативных путей
    и выбор наиболее уверенного.

    1. Получаем top-k первых токенов через logprobs
    2. Для каждого токена генерируем полный ответ
    3. Оцениваем уверенность каждого пути
    4. Возвращаем путь с максимальной уверенностью
    """
    # Шаг 1: получить top-k первых токенов
    first_token_response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": question}],
        max_tokens=1,
        logprobs=True,
        top_logprobs=k,
    )

    top_tokens = (
        first_token_response
        .choices[0]
        .logprobs
        .content[0]
        .top_logprobs
    )

    # Шаг 2: для каждого первого токена сгенерировать
    # полный ответ
    paths = []
    for token_info in top_tokens:
        prefix = token_info.token
        prefix_logprob = token_info.logprob

        # Генерируем продолжение, начиная с этого
        # токена, через assistant prefill
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "user", "content": question},
                {
                    "role": "assistant",
                    "content": prefix,
                },
            ],
            max_tokens=max_tokens,
            logprobs=True,
            top_logprobs=5,
            temperature=0,
        )

        choice = response.choices[0]
        full_text = prefix + (choice.message.content or "")

        # Шаг 3: вычислить уверенность пути
        confidence = compute_path_confidence(choice)

        paths.append({
            "first_token": prefix,
            "first_token_logprob": prefix_logprob,
            "full_text": full_text,
            "confidence": confidence,
        })

    # Шаг 4: выбрать путь с максимальной уверенностью
    best = max(paths, key=lambda p: p["confidence"])

    return {
        "answer": best["full_text"],
        "confidence": best["confidence"],
        "paths_explored": len(paths),
        "all_paths": paths,
    }


def compute_path_confidence(choice) -> float:
    """
    Метрика уверенности пути.
    Считаем среднюю дельту между top-1 и top-2
    логарифмическими вероятностями по всем токенам.
    Высокая дельта = модель уверена в каждом токене.
    """
    if not choice.logprobs or not choice.logprobs.content:
        return 0.0

    deltas = []
    for token_lp in choice.logprobs.content:
        top = token_lp.top_logprobs
        if len(top) >= 2:
            delta = top[0].logprob - top[1].logprob
            deltas.append(delta)

    return sum(deltas) / len(deltas) if deltas else 0.0


# Пример использования
result = cot_decoding(
    "Джон купил 3 пакета яблок по 4 штуки в каждом. "
    "Он отдал 5 яблок другу. Сколько яблок осталось?"
)
print(f"Ответ: {result['answer']}")
print(f"Уверенность: {result['confidence']:.3f}")
print(f"Путей исследовано: {result['paths_explored']}")

Реализация через Anthropic SDK

Anthropic API не возвращает logprobs напрямую, но можно использовать подход с принудительным префиксом (assistant prefill) для исследования альтернативных путей:

import anthropic

client = anthropic.Anthropic()

# Типичные CoT-стартеры, которые запускают
# рассуждения в моделях
COT_PREFIXES = [
    "",                     # без префикса (baseline)
    "Давайте разберёмся",   # русский CoT-стартер
    "Для решения",          # русский CoT-стартер
    "Let me think",         # английский CoT-стартер
    "Step 1:",              # пошаговый стартер
    "First,",               # последовательный стартер
]


def cot_decoding_anthropic(
    question: str,
    prefixes: list[str] = COT_PREFIXES,
    model: str = "claude-sonnet-4-20250514",
    max_tokens: int = 1024,
) -> dict:
    """
    CoT-Decoding для Claude через assistant prefill.
    Вместо top-k токенов используем заранее
    определённые CoT-стартеры.
    """
    paths = []

    for prefix in prefixes:
        messages = [{"role": "user", "content": question}]

        # Используем assistant prefill, если есть
        # префикс
        if prefix:
            messages.append({
                "role": "assistant",
                "content": prefix,
            })

        response = client.messages.create(
            model=model,
            max_tokens=max_tokens,
            messages=messages,
            temperature=0,
        )

        full_text = prefix + response.content[0].text

        # Эвристика уверенности: длина рассуждений
        # коррелирует с качеством ответа
        has_reasoning = any(
            marker in full_text.lower()
            for marker in [
                "шаг", "step", "значит", "итого",
                "получаем", "therefore", "thus",
                "сначала", "во-первых",
            ]
        )

        paths.append({
            "prefix": prefix or "(greedy)",
            "full_text": full_text,
            "has_reasoning": has_reasoning,
            "length": len(full_text),
        })

    # Предпочитаем пути с рассуждениями
    reasoning_paths = [p for p in paths if p["has_reasoning"]]
    best = (
        max(reasoning_paths, key=lambda p: p["length"])
        if reasoning_paths
        else paths[0]
    )

    return {
        "answer": best["full_text"],
        "prefix_used": best["prefix"],
        "has_reasoning": best["has_reasoning"],
        "paths_explored": len(paths),
    }

Продвинутая метрика: answer confidence

В оригинальной статье ключевая метрика — уверенность именно в финальном ответе (а не во всей цепочке):

import re


def extract_final_answer(text: str) -> str | None:
    """Извлечь финальный числовой ответ из текста."""
    patterns = [
        r"(?:ответ|answer)[:\s]*(\d+[\.,]?\d*)",
        r"(?:итого|total|=)\s*(\d+[\.,]?\d*)",
        r"(\d+[\.,]?\d*)\s*$",
    ]
    for pattern in patterns:
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            return match.group(1).replace(",", ".")
    return None


def cot_decoding_with_voting(
    question: str,
    k: int = 10,
    model: str = "gpt-4o",
) -> dict:
    """
    CoT-Decoding с голосованием по ответам.
    Комбинирует CoT-Decoding с Self-Consistency:
    - Генерируем k путей через top-k
    - Извлекаем финальный ответ из каждого
    - Взвешиваем ответы по confidence
    - Выбираем ответ с максимальным взвешенным счётом
    """
    result = cot_decoding(question, k=k, model=model)
    paths = result["all_paths"]

    # Группируем по ответам с взвешиванием
    answer_scores: dict[str, float] = {}
    answer_paths: dict[str, list] = {}

    for path in paths:
        answer = extract_final_answer(path["full_text"])
        if answer is None:
            continue

        weight = max(path["confidence"], 0.01)

        if answer not in answer_scores:
            answer_scores[answer] = 0
            answer_paths[answer] = []
        answer_scores[answer] += weight
        answer_paths[answer].append(path)

    if not answer_scores:
        return result

    best_answer = max(
        answer_scores, key=answer_scores.get
    )

    return {
        "answer": best_answer,
        "total_confidence": answer_scores[best_answer],
        "votes": len(answer_paths[best_answer]),
        "all_answers": answer_scores,
        "best_path": answer_paths[best_answer][0],
    }
  1. Не все API поддерживают logprobs. OpenAI API — да (logprobs=True, top_logprobs=5). Anthropic API — нет. Для Claude используйте подход с assistant prefill.
  2. Токен != слово. Top-k токенов — это не top-k слов. Токен может быть частью слова ("Дав", "айте"), и prefix continuation может сломать грамматику.
  3. Стоимость растёт линейно. k=10 означает 10 вызовов API. Для GPT-4o при 500 выходных токенов это ~$0.05 за один вопрос. Используйте k=3-5 для баланса цена/качество.
  4. Temperature=0 обязательна. При temperature > 0 greedy decoding нарушается, и результаты становятся нестабильными. CoT-Decoding работает с детерминированной генерацией.

Проверьте себя

Проверьте себя

1. Почему стандартный greedy decoding часто не находит цепочку рассуждений?

2. Как CoT-Decoding выбирает лучший путь из k альтернатив?

3. Какое главное открытие стоит за CoT-Decoding?