Seedance 2.5 is live — 30-second cinematic video with native audio & real-person references
Как проверить, что API LLM служит объявленную модель
2026/07/27

Как проверить, что API LLM служит объявленную модель

Языковые модели не могут выбрать случайное число. Этот дефект — это стабильная цифровая подпись для проверки, служит ли API объявленную модель.

Попроси языковую модель выбрать случайное число от 1 до 100 достаточно раз — и произойдёт что-то странное: ответы не случайны. Одна модель постоянно выбирает 42 и 73. Другая предпочитает 47 и 57. Паттерн стабилен, воспроизводим и отличается для каждой модели.

В июле 2026 года одна статья превратила эту странность в метод верификации. One Token Is Enough: Fingerprinting and Verifying Large Language Models from Single-Token Output Distributions показывает, что распределения ответов образуют надёжный поведенческий отпечаток — достаточный, чтобы проверить снаружи, служит ли API-endpoint объявленную модель[1]. Без весов, логитов, привилегированного доступа. Просто сотни односложных ответов.

В этом гайде объясняется, почему поле model — это скорее утверждение, чем гарантия, как работает отпечатков одного токена, что означают цифры и где границы метода.

TL;DR

  • Поле model в ответе API не поддаётся верификации протоколом. Ничто не доказывает, что запрос на флагманскую модель был ею обработан[1].
  • Языковые модели не могут выдать равномерное распределение. На наборе проб из статьи медианное распределение ответов содержит около 1.0 бита энтропии вместо 6.64 бит, которые давало бы равномерное число от 1 до 100[1].
  • Этот дефект постоянен, поэтому он работает как подпись. Одна модель, один скос, каждый раз.
  • На шкале есть разделение: одна модель против себя даёт JSD около 0.14, одна модель у двух провайдеров — около 0.23, две разные модели — около 0.46[1].
  • Точность высока, но не идеальна. Равнополовинная ошибка — 10.6% при 8 пробных единицах и 7.3% при полном наборе 40, AUC = 0.971[1].
  • Несовпадение — это признак, не доказательство. Квантование, тихое обновление версии, скрытый системный промпт — всё это смещает распределение без обмана.

Ты не видишь, что скрывается за API

Когда ты обращаешься к endpoint чат-дополнений, отправляешь текст и получаешь текст. Поле model в ответе содержит то, что решил туда положить сервер. Ничто в протоколе не доказывает, что запрос был обработан моделью с этим именем, а не чем-то в десять раз дешевле[1].

Эта щель становится критична, когда всё больше рынка работает через посредников: агрегаторы перепродают сотни моделей через один endpoint, региональные реселлеры предлагают флагманский доступ ниже официальной цены, сторонние хосты сервят open-weight модели с выбранным квантованием и стеком развёртывания, который ты не видишь[1].

Большинство таких бизнесов честные. Но стимул к обману очевиден, и отдельный аудит 17 shadow-API операторов обнаружил несколько endpoint'ов, которые не прошли верификацию по объявленным моделям[2].

Дело не только в мошенничестве. Провайдер может квантовать модель, чтобы сэкономить на обслуживании, развернуть тихую версию, или маршрутизировать трафик через смешанные бэкенды. Если качество твоего продукта зависит от конкретной модели, вопрос "что я на самом деле получаю?" должен иметь ответ на основе фактов, а не доверия.

Почему случайные числа выдают игру

Метод опирается на хорошо задокументированный дефект. Языковые модели не вычисляют — они предсказывают, поэтому при запросе случайного числа воспроизводят смещения своих данных обучения и настройки предпочтений[1].

Иллюстрация смещённых распределений ответов, когда языковой модели просят выбрать случайное число от 1 до 100. Видна сильная концентрация на культурно значимых числах вроде 42, 7 и 73 против плоского равномерного распределения

Три кластера доминируют:

  • 42 представлено непропорционально часто — ответ из «Справочника путешественника по Галактике», отзывающийся сквозь десятилетия интернет-текстов.
  • 7 носит тысячелетия культурного веса как счастливое число, к которому тянутся люди.
  • 37, 47, 73 и другие двузначные простые числа кажутся случайными людям, поэтому доминируют в человеком сгенерированных примерах «случайных» чисел, на которых обучалась модель.

Статья квантифицирует коллапс: медианное распределение ответов несёт примерно 1.0 бит энтропии, тогда как честный выбор от 1 до 100 нёс бы 6.64 бита[1]. Если честная кость имеет сто граней, большинство моделей ведут себя как слегка смещённая монета.

Ключевой момент в том, что дефект постоянен. Одна модель выдаёт одно и то же смещённое распределение каждый раз, а разные модели, включая сестринские версии в одном семействе, выдают измеримо разные. Баг становится сигнатурой.

Протокол в четыре шага

1. Зондирование. Задай endpoint батарею односложных вопросов: выбери случайное число от 1 до 100, назови случайный цвет, подбрось монету. Статья использует 10 заданий на 4 языках для 40 пробных единиц, дискретизируя каждую 30 раз при temperature 1.0, max_tokens=16 и отключённым reasoning[1].

2. Отпечаток. Для каждой пробной единицы составь ответы в эмпирическое распределение. Набор этих распределений — поведенческий отпечаток endpoint'а.

3. Сравни. Измерь расстояние между отпечатком и доверенной эталоном для объявленной модели, используя дивергенцию Jensen-Shannon по основанию 2, так что шкала идёт от 0 (идентично) к 1 (не пересекаются).

4. Решай. Малое расстояние означает согласованность с утверждением. Большое означает, что endpoint поведенчески совсем другой.

Две свойства делают это трудным для обхода. Не требует спецдоступа — всё, что отвечает на чат-дополнения, можно отпечатать. И нет магической строки для фильтрации, потому что каждое зондирование — обычный безвредный вопрос из пулов перефразировок, так что нечестный middlebox не может внести специальный случай без нарушения обычного трафика[1].

Чтение числа расстояния

Значение дивергенции ничего не значит без опорных точек, и здесь метод становится практичен.

Шкала дивергенции Jensen-Shannon с опорными линиями: 0.14 для одной модели против себя, 0.23 для одной модели у двух провайдеров, 0.46 для двух разных моделей

СравнениеТипичное JSD
Одна модель против себя~0.14
Одна модель, два разных провайдера~0.23
Две разные модели~0.46

Между «одно и то же» и «разное» есть реальный просвет[1]. Заметь, что означает средняя строка: даже честное развёртывание одной модели другим хостом смещается измеримо, потому что квантование, стек развёртывания и скрытые системные промпты оставляют следы.

Точность масштабируется с числом проб. При 8 пробных единицах равнополовинная ошибка — 10.6%, при полных 40 — 7.3%, AUC = 0.971[1].

Что статья нашла в природе

Случай palmyra-x5. Самый поразительный результат статьи касается модели, предлагаемой как закрытый флагман, чей отпечаток находился на расстоянии JSD 0.141 от open-weight модели объёмом 235B, статистически неотличимо от ~0.140, который получается при сравнении модели с самой собой. Поведенчески, заключает статья, endpoint служил функционально эквивалентную open-source модель[1].

Одна модель, разные провайдеры, иногда подозрительно разные. Из 34 пар одной модели у разных провайдеров 10 отклонились за пределы 5-го процентиля распределения подделок[1]. Некоторые официальные развёртывания моделей сторонних разработчиков дрейфуют настолько, что выглядят как разные модели. Верификация — не параноя даже когда никто не врёт о названии.

Исследовательские артефакты открыты: датасет отпечатков опубликован под CC-BY-4.0, код воспроизведения под MIT, оба на Zenodo[3][4].

Запусти проверку сам

Протокол простой достаточный для прямой реализации. Его структура:

import collections, math
from openai import OpenAI

client = OpenAI(api_key="...", base_url="https://your-endpoint/v1")

def probe(model, prompt, n=25):
    counts = collections.Counter()
    for _ in range(n):
        r = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=1.0,
            max_tokens=16,
        )
        counts[r.choices[0].message.content.strip()] += 1
    total = sum(counts.values())
    return {k: v / total for k, v in counts.items()}

def jsd(p, q):
    keys = set(p) | set(q)
    m = {k: 0.5 * (p.get(k, 0) + q.get(k, 0)) for k in keys}
    def kl(a):
        return sum(a[k] * math.log2(a[k] / m[k]) for k in a if a[k] > 0)
    return 0.5 * kl(p) + 0.5 * kl(q)

Четыре практических совета для правильного выполнения.

Зафиксируй условия дискретизации. Temperature 1.0, маленький max_tokens, reasoning отключен. Отпечаток, собранный под другие настройки, несравним с собранным под статью.

Используй больше одного зондирования. Один вопрос шумный. Ошибка примерно вдвое уменьшается, переходя от 8 пробных единиц к 40.

Сравнивай с эталоном, собранным так же. Чистейший эталон — официальный endpoint для одной модели, отпечатанный в одной сессии под идентичными настройками, а не опубликованная таблица собранная месяцами ранее.

Смотри и вспомогательные сигналы. Отпечаток, не согласующийся сам с собой при разбиении на половины, намекает на мультибэкенд-маршрутизацию. Односложные ответы, биллящиеся сотнями completion-токенов, намекают на padding. Раздутые prompt-токены намекают на большой скрытый системный промпт.

Стандартная проверка из 8 пробных единиц при 25 выборках — это примерно 200 крошечных запросов, что стоит доли цента на малой модели.

Что результат значит и не значит

Будь точен в интерпретации, что поддерживает этот метод.

Несовпадение — признак, не доказательство. Метод имеет встроенную частоту ошибок, около 10.6% EER при 8 пробных единицах. Агрессивное квантование, тихое обновление модели, устаревший эталон или скрытый системный промпт сервера могут все сместить распределения без намерения обмана. Красный результат — причина для переделки с большим числом проб, теста второго эталона и вопросов[1].

Совпадение сильно, но не абсолютно. Утончённая подделка могла бы в принципе имитировать распределения другой модели, хотя проделать это на дюжинах перефразированных многоязычных зондирований при правильном обслуживании обычного трафика — сложнее, чем звучит.

Моделям с reasoning нужен уход. Отпечатки собираются с reasoning отключен. Где endpoint не может его отключить, уверенность падает.

Расстояние — свойство endpoint'а в момент времени, не приговор бизнесу.

FAQ

Что такое LLM fingerprinting?

Измерение распределения ответов модели на батарею односложных вопросов, потом сравнение этого распределения с эталоном для модели, которую endpoint говорит что служит[1].

Почему языковые модели не могут генерировать случайные числа?

Они предсказывают, не вычисляют, поэтому запрос на случайность возвращает смещения данных обучения и настройки предпочтений. Культурно значимые значения вроде 42 и 7 доминируют, коллапсируя распределение примерно к 1.0 биту энтропии против идеального равномерного 6.64[1].

Какое различие считается несовпадением?

Используй эталоны статьи вместо фиксированного порога: около 0.14 для модели против себя, 0.23 для одной модели между провайдерами, 0.46 для разных моделей[1].

Сколько запросов требует проверка?

Полный протокол статьи — 40 пробных единиц по 30 выборок каждая. Более лёгкая 8-единичная проверка при 25 выборках — примерно 200 запросов и поднимает равнополовинную ошибку с 7.3% до 10.6%[1].

Может ли провайдер обнаружить и обойти тест?

Нелегко. Зондирования — обычные безвредные вопросы из пулов перефразировок, так что внести специальный случай без нарушения обычного трафика трудно[1].

Означает ли провалившаяся проверка, что провайдер врёт?

Нет. Квантование, тихие обновления версий, скрытые системные промпты и устаревшие эталоны все производят дрейф без обмана. Расстояние — статистическое наблюдение, заслуживающее более пристального изучения.

Датасет доступен?

Да. Датасет отпечатков на Zenodo под CC-BY-4.0, код воспроизведения под MIT[3][4].

Отношение к полю model как к утверждению

Полезный сдвиг здесь маленький и специфичный. Поле model в ответе API — утверждение, и теперь есть дешёвый, открытый, статистически обоснованный способ проверить это утверждение снаружи, построенный на ничём более экзотичном, чем факт, что языковые модели не могут назвать случайное число в целях самосохранения.

Если ты покупаешь ёмкость через любого посредника, правильное место для этого — рядом с мониторингом аптайма: периодическая проверка против эталона, собранного тобой самим, со вспомогательными сигналами, наблюдаемыми рядом с дистанцией. И правильный способ прочитать красный результат — как начало разговора, не конец. Верифицировать LLM API — собирать свидетельства об endpoint'е в момент времени, что стоит делать ровно потому, что альтернатива — предположение.

Ссылки

  1. Bruckner, Tomáš. One Token Is Enough: Fingerprinting and Verifying Large Language Models from Single-Token Output Distributions. arXiv, July 2026. arxiv.org/abs/2607.10252
  2. CISPA researchers. Real Money, Fake Models — an audit of shadow LLM API operators. arXiv. arxiv.org/abs/2603.01919
  3. LLM fingerprint dataset (models × tasks × languages). Zenodo, CC-BY-4.0. zenodo.org
  4. Reproduction code for One Token Is Enough. Zenodo, MIT License. zenodo.org

Further reading