Всем привет, меня зовут Сергей Прощаев и в этой статье расскажу про то, что произошло с табличными данными летом 2026 года: появилась модель, которая делает предсказание по таблице вообще без обучения, и первые же замеры показали, что она обходит настроенный градиентный бустинг.

Я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E‑commerce, преподаю на курсах разработки и архитектуры в ОТУС. Сразу оговорюсь: я не датасаентист. Но в одном проекте я довольно близко наблюдал за жизнью скоринговой модели на CatBoost — со стороны сервисов, которые её дёргают, и команды, которая держит всё это в проде. И когда мне в рабочий чат прислали ссылку с заголовком в духе «XGBoost всё, приехали», я отреагировал не «ух ты», а «и кто теперь будет это переписывать».

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

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

Ниже: что выпустили, где цифры честные, а где сравнение подкручено самой постановкой эксперимента, и что с этим делать бэкендеру, у которого табличная модель стоит в критическом пути.

Рис. 1. Два подхода к предсказанию на табличных данных: цикл подбора против одного прохода
Рис. 1. Два подхода к предсказанию на табличных данных: цикл подбора против одного прохода

Почему деревья держались пятнадцать лет

Табличные данные — это последний рубеж, который глубокое обучение не забрало. Текст, картинки, звук, видео — везде фундаментальные модели победили. А в базах данных, где лежит основная масса корпоративных данных, продолжали выигрывать XGBoost, LightGBM и CatBoost.

Причины известны и вполне инженерные: разнотипные колонки, пропуски, категориальные признаки, маленькие выборки и чувствительность к мусорным фичам. Деревья со всем этим справляются из коробки. Каждые год‑два появлялся очередной нейросетевой подход, который «побеждал бустинг», практики находили изъян в постановке сравнения, и бустинг оставался на месте.

Цена этого господства — бесконечный цикл: инженерия признаков, перебор гиперпараметров, переобучение под каждый новый датасет. На том скоринге, о котором я говорю, это буквально отдельный сервис с расписанием.

Мне как‑то попалась оценка, что львиная доля времени в табличном проекте уходит не на модель, а на подготовку данных и подбор. Похоже на правду, только я бы добавил туда ещё одну статью расходов, о которой обычно не пишут.

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

Что выпустили 30 июня

TabFM от Google Research — модель, которая читает таблицу как единый контекст: обучающие строки и строки, для которых нужен прогноз, подаются вместе, и результат возвращается за один проход через замороженные веса. Позиционируется как табличный родственник TimesFM, который до этого сделал то же самое для временных рядов.

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

Ключевая деталь, на которой я застрял минут на десять: вызов fit() здесь ничего не обучает. Он сохраняет контекст.

# (Python)
from tabfm import TabFMClassifier, tabfm_v1_0_0_pytorch as tabfm_v1_0_0

model = tabfm_v1_0_0.load()          # веса тянутся с Hugging Face
clf = TabFMClassifier(model=model)
clf.fit(X_train, y_train)            # градиентного спуска не происходит,
                                     # данные просто кладутся в контекст
preds = clf.predict(X_test)          # один forward pass, веса заморожены

API совместим со scikit‑learn — то есть с точки зрения человека, который встраивает это в сервис, интерфейс не отличается от привычного. Внутри механика другая: чередующиеся слои внимания по строкам и по столбцам, сжатие строки в вектор через CLS‑токены, а поверх — причинный трансформер, для которого ваши обучающие строки работают как промпт.

И вот здесь две вещи, которые я бы выяснил до того, как показывать демо коллегам.

  • Первая — установка. Пакет, лежащий на PyPI, не умеет подгружать выпущенные веса, ставить нужно из репозитория. Мелочь, но полдня можно потерять на ровном месте.

  • Вторая важнее и в обзорах почти не встречается. У scikit‑learn‑обёрток есть параметры max_num_features и max_num_rows, и по умолчанию это 500 признаков и сто строк контекста. Если таблица больше, модель не проглатывает её целиком — она работает с выборкой. То есть фраза «читает всю вашу таблицу» верна только до определённого размера, а дальше начинается сэмплирование, о котором надо знать заранее, иначе объяснить разброс результатов будет нечем.

Идея не с нуля. Линия тянется от TabPFN (2022), который первым показал, что таблицу можно решать через in‑context learning: модель предобучают на синтетических таблицах, а не на ваших. Дальше были TabPFN v2, TabICL, CARTE, версия TabPFN-2.5, которая дотянулась примерно до ста тысяч строк и двух тысяч признаков. TabFM — следующий шаг в этой линии, а не гром среди ясного неба.

Чтобы разница в пайплайнах была наглядной, посмотрите на Рис. 2. Слева — то, как устроено большинство табличных проектов сегодня, справа — то, что предлагает подход foundation‑модели.

Рис. 2. Схема принципиальная: цикл обучения против одного прохода
Рис. 2. Схема принципиальная: цикл обучения против одного прохода

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

Что заявлено и где заканчиваются доказательства

Модель проверяли на TabArena — это 51 датасет, из них 38 на классификацию и 13 на регрессию, размером примерно от семисот до ста пятидесяти тысяч строк. В режиме одного прохода без всякого перебора гиперпараметров TabFM обходит тщательно настроенные базовые модели, включая градиентный бустинг. Есть и вторая конфигурация — ансамбль из 32 прогонов с кросс‑признаками, SVD‑признаками и блендингом через неотрицательный МНК. У ансамбля цифры, разумеется, лучше.

И вот здесь у меня как у человека, который много раз видел презентации в жанре «наш продукт быстрее конкурента», сработал рефлекс. Сравнение «zero‑shot без настройки против настроенного XGBoost» звучит красиво, но за XGBoost стоят годы накопленного фольклора по подбору гиперпараметров именно под публичные бенчмарки.

Я бы вообще поменял постановку вопроса. Спрашивать «какая модель лучше» — незрелая формулировка, потому что ответ всегда зависит от того, сколько вы готовы потратить.

Правильная формулировка — качество как функция от бюджета: при пятнадцати минутах на задачу выигрывает одно, при восьми часах подбора — другое, а при неделе инженерии признаков картина может перевернуться ещё раз. Именно такую кривую я бы хотел видеть в релизах, и именно её приходится строить самому.

Теперь чего в публикации нет. Попарных цифр против XGBoost, CatBoost и более ранних табличных foundation‑моделей там не приводится: заявлена конкурентоспособность на бенчмарке в целом, а разбивки «на этом датасете столько, на том столько» относительно конкретных базовых моделей публика не получила.

Второй пробел серьёзнее — нет проверки на грязных корпоративных таблицах: дрейф распределения, редкие категориальные значения, утечки таргета. То есть ровно на том, что решает судьбу табличной модели в реальном сервисе.

Здесь надо отдать должное: Google об этом честно предупреждает, просто не в той части материалов, которую все читают. В карточке модели прямо написано, что обучение шло целиком на синтетике, что поведение на конкретных реальных доменах и на малых группах не охарактеризовано и что перед применением в ответственных сценариях нужно проверять модель на своей отложенной выборке. Проблема не в том, что кто‑то что‑то скрыл. Проблема в том, что до этой оговорки доходит примерно никто, а до заголовка «обошла настроенный XGBoost» — все.

Вспомните, на чём модель предобучали. Сотни миллионов синтетических таблиц из структурных причинных моделей — это красивая и чистая вселенная. Реальная таблица со скорингом красивой не бывает никогда: там есть колонка, которую пять лет назад завели «временно», и три значения статуса, означающие одно и то же. Между «конкурентоспособна на TabArena» и «работает у вас» лежит именно этот зазор, и его никто пока не измерил.

История, ради которой стоило копать

Мне попался разбор Yash Raj Pandey, который сделал ровно то, чего я жду от инженера: попытался сломать чужой результат, а когда не сломал — зафиксировал, где именно улучшил.

Он прогнал TabFM независимо, на трёх машинах. На малых и средних таблицах модель действительно обошла корректно настроенный через Optuna XGBoost на всех десяти сопоставленных по фолдам датасетах. На опорном наборе разрыв под тюнингом даже вырос: 0,877 против 0,821 по точности. Казалось бы, всё, расходимся.

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

Дальше — сюжет, знакомый любому, кто выкатывал что‑то на GPU. Заявленные 22,75 ГБ видеопамяти оказались в основном артефактом аллокатора XLA: реальный расход около 16,95 ГБ, а отключение преаллокации примерно удвоило рабочий контекст, с десяти тысяч строк до двадцати. После того как в репозитории включили bf16 и чанкинг активаций, бэкенд на PyTorch стал держать 3–7 ГБ вместо фиксированных семнадцати у JAX, вмещать сорок тысяч строк там, где JAX падал по памяти на двадцати, и работать в два‑четыре раза быстрее на большом контексте. Попутно нашёлся баг, роняющий predict на любой машине с двумя и более GPU.

Могу себе представить, как выглядел бы такой инцидент: команда берёт первую версию, верит в 22,75 ГБ, закладывает инстанс под неё, а потом полдня ищет, почему на двухкарточной ноде всё падает. Это не гипотетика, это обычный вторник.

Как проверить за вечер: протокол

Минимальный каркас для честного сравнения выглядит так. Ключевое здесь не модель, а условия замера:

# (Python)
import numpy as np
import optuna
from catboost import CatBoostClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import roc_auc_score

SEEDS = [0, 1, 2, 3, 4]      # без нескольких сидов результат не считается
BUDGET_SECONDS = 900         # одинаковый бюджет на подбор для обеих сторон
CAT_FEATURES = ["region", "product", "channel"]   # фиксируем явно, не угадываем

def evaluate(make_model, X, y):
    scores = []
    for seed in SEEDS:
        cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=seed)
        for train_idx, test_idx in cv.split(X, y):
            X_tr, X_te = X.iloc[train_idx], X.iloc[test_idx]
            y_tr, y_te = y.iloc[train_idx], y.iloc[test_idx]
            model = make_model(seed, BUDGET_SECONDS, X_tr, y_tr)
            proba = model.predict_proba(X_te)[:, 1]
            scores.append(roc_auc_score(y_te, proba))
    return np.mean(scores), np.std(scores)

def make_boosting(seed, budget, X_tr, y_tr):
    # весь бюджет уходит в подбор; сид фиксируется и в сэмплере, и в модели
    def objective(trial):
        params = {
            "depth": trial.suggest_int("depth", 4, 10),
            "learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3, log=True),
            "l2_leaf_reg": trial.suggest_float("l2_leaf_reg", 1.0, 10.0, log=True),
        }
        inner = StratifiedKFold(n_splits=3, shuffle=True, random_state=seed)
        folds = []
        for tr, va in inner.split(X_tr, y_tr):
            m = CatBoostClassifier(**params, iterations=2000, random_seed=seed,
                                   cat_features=CAT_FEATURES, verbose=0)
            m.fit(X_tr.iloc[tr], y_tr.iloc[tr],
                  eval_set=(X_tr.iloc[va], y_tr.iloc[va]),
                  early_stopping_rounds=100)          # иначе бюджет уйдёт в шум
            folds.append(roc_auc_score(y_tr.iloc[va],
                                       m.predict_proba(X_tr.iloc[va])[:, 1]))
        trial.set_user_attr("best_iteration", m.get_best_iteration())
        return float(np.mean(folds))

    study = optuna.create_study(direction="maximize",
                                sampler=optuna.samplers.TPESampler(seed=seed))
    study.optimize(objective, timeout=budget)
    best_iters = study.best_trial.user_attrs["best_iteration"]
    model = CatBoostClassifier(**study.best_params, iterations=best_iters,
                               random_seed=seed, cat_features=CAT_FEATURES, verbose=0)
    model.fit(X_tr, y_tr)
    return model

def make_tabfm(seed, budget, X_tr, y_tr):
    # подбирать нечего; проверьте лимит контекста, иначе строки будут сэмплированы
    from tabfm import TabFMClassifier, tabfm_v1_0_0_pytorch as tabfm_v1_0_0
    clf = TabFMClassifier(model=tabfm_v1_0_0.load(), max_num_rows=len(X_tr))
    clf.fit(X_tr, y_tr)
    return clf

Обратите внимание на две детали.

  • Первая: BUDGET_SECONDS уходит в обе стороны и там, где есть что подбирать, реально тратится на подбор. Без этого весь эксперимент превращается в демонстрацию, а не в замер.

  • Вторая: max_num_rows выставлен явно. Оставите по умолчанию — сравните настроенный бустинг на всех данных со ста строками контекста и сделаете неверный вывод.

Результаты имеет смысл сводить в такую форму, иначе через неделю сами не вспомните, при каких условиях получили цифру:

Колонка

Что записываем

Модель и версия

CatBoost 1.2.x / TabFM 1.0.0, бэкенд PyTorch или JAX

Режим данных

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

Размер контекста

сколько строк реально ушло в модель, а не сколько было в таблице

Метрика

среднее и стандартное отклонение по всем сидам, не одно число

Бюджет

одинаковые секунды на обе стороны, зафиксировать явно

Память и железо

пиковая на GPU замеренная, модель карты, версия CUDA, число карт

Последние строки не формальность. Судя по опыту тех, кто уже прогонял, основная возня оказывается не в качестве, а в окружении: версии драйверов, поведение аллокатора, поддержка нескольких карт. Качество меряется за час, инфраструктура — за неделю.

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

Где вывод переворачивается

Теперь важное, что теряется в заголовках. Преимущество foundation‑моделей не универсально — оно зависит от режима данных.

В опубликованных независимых прогонах по девятнадцати датасетам картина такая: на крупных числовых наборах XGBoost и TabICL показали результат лучше, на малых выборках впереди оказались foundation‑модели, а на смешанных данных разрыв в их пользу был шире всего.

Оговорюсь: это результаты конкретных замеров, а не установленный закон природы. Но объяснение выглядит логично — предобученные априорные представления ценны, когда своих данных мало; когда данных много, бустинг выучивает взаимодействия признаков напрямую и обходится без чужих априори.

Отдельно держу в голове, что TabArena — бенчмарк, к которому причастны исследователи из той же лаборатории, что делала TabPFN. Ничего криминального, но независимый бенчмарк и собственный холдаут никто не отменял.

Жёсткие ограничения текущего релиза, которые надо знать до того, как трогать прод:

  • потолок в 10 классов на классификации — это архитектурное ограничение, а не настройка;

  • около 500 признаков, на более широких таблицах поведение деградирует;

  • память растёт вместе с числом строк контекста, а контекст — это ваши данные;

  • код под Apache 2.0, а веса — под некоммерческой лицензией.

Последний пункт для продуктовой команды означает ровно одно: в текущем виде это инструмент для прототипов и исследований, а не для сервиса, который зарабатывает деньги.

И три вещи, которые я бы проговорил с командой ещё до пилота.

  1. Латентность. Показательный замер: на одном небольшом датасете все модели дали одинаковую стопроцентную точность, но XGBoost отработал за 0,4 секунды, а TabFM — за 49,2 секунды на процессоре и 3,6 секунды на видеокарте. Бустинг на CPU оказался в девять раз быстрее, чем foundation‑модель на GPU. Датасет там учебный, и на других объёмах соотношение будет другим — но порядок величины показателен: в синхронном сценарии одобрения заявки это не абстракция, а SLA, и GPU в критическом пути придётся резервировать.

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

  3. Воспроизводимость задним числом. Артефакт модели можно положить в реестр и достать через год. Контекст надо ещё уметь зафиксировать, сохранить целиком и доказать, что он был именно таким.

Как выбирать: план действий

Чтобы не гадать, я свёл принятие решения в короткое дерево — Рис. 3. Оно не про то, что «лучше», а про то, что разумно попробовать первым при ваших вводных.

Что здесь главное: ни одна ветка не заканчивается словами «выкинуть бустинг». Меняется не победитель, а точка входа в проект. Раньше первый день новой табличной задачи уходил на сборку baseline, теперь baseline можно получить за один forward pass и сразу понять, есть ли в данных вообще сигнал.

Практики команд, которые уже это гоняют

  • Равный бюджет вычислений в любом сравнении. Час на одну сторону — час на другую. Иначе это не эксперимент, а презентация.

  • Несколько сидов обязательно. Разница меньше разброса по сидам — это ничья, а не победа. Готовность понизить собственный результат до ничьей отличает замер от рекламы.

  • Проверять, сколько строк реально дошло до модели. Лимит контекста по умолчанию способен незаметно превратить ваш эксперимент в сравнение несравнимого.

  • Смотреть не только на метрику, но и на аллокатор. История с 22,75 ГБ, которые оказались 16,95, — напоминание: цифры из README проверяются на вашем железе.

  • Лицензия проверяется до пилота, а не после. Открытый код и некоммерческие веса — разные вещи, и юристы объяснят разницу быстрее, чем хотелось бы.

Что в итоге

Мой вывод, который я обычно формулирую для команды одной фразой: XGBoost не умер, у него появился быстрый разведчик.

Никто в здравом уме не пойдёт сейчас переписывать работающий скоринг. Но первый шаг в чек‑листе новых табличных задач я бы уже поменял: сначала zero‑shot прогон foundation‑моделью на пилотном контуре, чтобы за пятнадцать минут понять, какой потолок качества в данных вообще есть, и только потом решение о полноценном пайплайне с ретрейном. Это экономит не проценты метрики, а недели работы там, где сигнала в данных изначально мало.

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

И да, через год‑полтора кто‑нибудь снова напишет, что бустинг умер. Проверять всё равно придётся руками.

Когда новый подход выглядит убедительно на бенчмарке, главный вопрос быстро смещается от «кто победил» к «как это вообще жить будет в проде». Если хочется разбирать ML не только на уровне метрик, но и через архитектуру системы, ограничения инфраструктуры и выбор подходящей модели под задачу, приходите на открытые уроки:

  • 19 августа, 20:00. «Почему 90% ML-проектов не доходят до продакшена? Разбираем архитектуру настоящей ML-системы». Записаться.

  • 24 августа, 20:00. «Современные модели прогнозирования — TimesNet, TimeGPT и новое поколение моделей временных рядов». Записаться.

Полный список бесплатных уроков августа смотрите в дайджесте.