
Идентификация OpenAI-совместимых LLM по сигналам токенизатора и поведения.

Снимает отпечаток serving-стека, стоящего за OpenAI-совместимым chat-эндпоинтом.
Он фиксирует учёт токенизатора, скрытое смещение шаблона, текст валидации, форму JSON/SSE, принимаемые роли, именованные лимиты и заголовки шлюза. Он не загружает веса, не хранит API-ключи и не спрашивает модель, кто она.
| Импорт | lm_fingerprint |
| CLI | lm-fingerprint |
| Что это | Версионируемые инфраструктурные пробы против /chat/completions. Офлайн-синтетическая библиотека. Локальная проверка токенизатора Hugging Face (без весов). |
| Чем это не является | Не градиентная атака. Не джейлбрейк. Не идентификация по encode-map (tokprint). Не «какой бинарник на этом порту» (Julius). |
Только авторизованное использование: эндпоинты, которыми вы владеете, лаборатории, которые вы контролируете, баг-баунти в рамках скоупа, письменные пентесты. См. SECURITY.md.
--api-key-envзадаёт имя переменной окружения. Ключ никогда не логируется.
OpenAI-совместимый чат — это общий провод. vLLM, SGLang, лабораторный шлюз и
реселлер — все могут говорить на /chat/completions. Поле model — это метка.
Хост может подменить бэкенд, обернуть токенизатор или поставить роутер
спереди. Пробы личности сдвигаются, когда сдвигается системный промпт.
Что остаётся неизменным — это сантехника: как сервер считает токены, сколько
скрытых токенов шаблона он добавляет, текст, написанный его валидатором, диалект
SSE, какие роли он принимает, число, которое он называет, когда max_tokens
абсурден, заголовки Server / провайдера.
Именно на этот вопрос отвечает инструмент: какой стек обрабатывает этот chat-путь? Не какой чекпоинт, не какой процесс привязан к порту, не кто написал completion.
ModelPrint задавал тот же вопрос в браузере с девятью пробами. Этот пакет — форма CLI и библиотеки: версионируемые пробы, библиотека для enrollment, попарные доказательства, дрейф и локальный путь токенизатора, который никогда не обращается к хостинговому Inference API.
Отпечаток — это версионируемая JSON-запись value проб. Каждая проба
должна возвращать одно и то же значение при повторном обращении к тому же стеку. Временные метки,
request id, задержка и ключи вырезаются.
Счётчики токенизатора переиспользуют тексты ModelPrint 1.0.0 (MIT, unclecode/modelprint),
побайтово точные, минус односимвольный базовый "a", чтобы скрытый шаблон
сократился. Эти четыре числа — проекция encode-map (T).
Равные счётчики не означают (T_1 \equiv T_2). GPT-2 и OPT совпадают на
них; хеш rust-пайплайна — нет.
hf_identity (только локальный HF-путь) — это SHA-256 от этого обязательства пайплайна
плюс хеш chat-шаблона. Одинаковые (T) и одинаковый шаблон совпадают
(tiny-gpt2 / gpt2). Это семейство + шаблон, а не чекпоинт.
Структура:
src/lm_fingerprint/
cli.py argparse. --api-key-env only. No --api-key.
client.py HttpTransport, InProcessTransport, redact_secrets
probes.py 38 ProbeSpec rows (tokenizer / errors / shape / roles / limits / proxy / surface)
engine.py run suite → Fingerprint
schema.py schema_version=1, probe_suite_version=lm-fingerprint-probes-v2
similarity.py weighted exact-match, coverage, confidence, library rank, drift
align.py constant-shift MAE + residual on the 15-script suite
catalog.py offline supported_parameters twins
library.py enroll / match. Packaged rows are synthetic fixtures
synthetic.py in-process stacks for tests and the shipped library
hf.py local AutoTokenizer + named validation profile. No weights
localmap.py encode-pipeline-v2 commitment (rust tokenizer.json)
proof.py measured ModelPrint 9-probe identity
report.py text reports
data/library.json
Два пути к одному и тому же набору проверок:
live endpoint local Hub tokenizer (optional [hf])
| |
HttpTransport.chat HfChatTransport
POST /chat/completions encode / chat_template for counts
| synthetic profile for errors/shape
+------------------+----------------------+
|
engine.run_probes
|
Fingerprint (JSON, keyless)
|
compare | match | drift | enroll
Создание проб: docs/PROBES.md. Карта модулей: docs/ARCHITECTURE.md.
| Что у вас есть | Что вы можете наблюдать | Что делает этот инструмент |
|---|---|---|
| Авторизованный OpenAI-совместимый base URL + ключ | HTTP чата: usage, ошибки, заголовки, SSE | fingerprint / compare / match / drift |
| Локальные файлы токенизатора | encode и chat_template | fingerprint-hf / verify-hf. Веса остаются на диске |
| Ничего, кроме host:port | Баннер / /health / /api/tags | Вне области применения. Используйте Julius |
Совпадение — это общая инфраструктура. Одна лаборатория может обслуживать две контрольные точки на одном стеке. Маршрутизатор может отвечать собственной обёрткой ошибок. Согласие по счёту не является идентичностью (T).
Python 3.10+. Основная среда выполнения — стандартная библиотека.
git clone https://gitlab.com/WattoCyber/lm-fingerprint.git
cd lm-fingerprint
pip install -e ".[dev]"
PYTHONPATH=src python3 scripts/repro.py
Ожидается REPRO_OK. Этот шлюз работает офлайн: без GPU, без production API.
Локальные тесты HF пропускаются, если токенизатор не закэширован.
pip install -e ".[hf]" # transformers, for fingerprint-hf / verify-hf
lm-fingerprint list-probes
lm-fingerprint list
list выводит упакованные стеки: openai-chat-strict, glm-compat,
router-openai, permissive-compat. Это фикстуры, а не спарсенные
провайдеры. Сравните два из них без сети:
from lm_fingerprint.client import InProcessTransport
from lm_fingerprint.engine import fingerprint_endpoint
from lm_fingerprint.similarity import compare_fingerprints
from lm_fingerprint.synthetic import SYNTHETIC_STACKS, handler_for
def fp(stack_id):
t = InProcessTransport(handler_for(stack_id), headers=SYNTHETIC_STACKS[stack_id].headers)
return fingerprint_endpoint(t, model=f"synthetic/{stack_id}", base_url=f"inprocess://{stack_id}")
print(compare_fingerprints(fp("openai-chat-strict"), fp("router-openai"))["note"])
Тот же токенизатор слов, другие ошибки и заголовки. Оценка около
0.62. Четыре счётчика ModelPrint совпадают; этот инструмент не называет
это общим стеком.
lm-fingerprint fingerprint \
--base-url https://your-lab.example/v1 \
--model the-label \
--api-key-env OPENAI_API_KEY \
--out lab.json