Присоединяйтесь к бете Hanzo и получите скидку 50% на любой платный тариф

BitDelta: обслуживание 14 моделей Zen с одного GPU-кластера

Как 1-битное дельта-сжатие BitDelta позволяет обслуживать 14 вариантов моделей Zen на общей GPU-инфраструктуре: математика, архитектура и компромиссы.

На этой странице

Экономика обслуживания дообученных вариантов модели враждебна по умолчанию.

Модель на 70B параметров в BF16 требует 140GB памяти GPU. Если есть 14 дообученных вариантов этой модели — каждый со своим профилем возможностей, предметной специализацией или поведенческой настройкой — наивная архитектура обслуживания требует 14 отдельных загрузок модели: 1.96TB памяти GPU, 14 отдельных процессов инференса, 14x стоимости инфраструктуры. За каждый вариант после первого вы платите полную цену за то, что почти целиком является продублированным базовым знанием.

BitDelta (arXiv:2402.10193) ломает эту экономику. Идея изящна, а математика проста.

Математическое основание

Пусть W_base — матрица весов базовой модели, а W_ft — соответствующая матрица весов в дообученном варианте. Поведенческая дельта такова:

Δ = W_ft - W_base

BitDelta сжимает эту дельту до точности 1 бит:

W_ft ≈ W_base + α · sign(Δ)

Здесь α — масштабный коэффициент на матрицу, равный среднему абсолютному значению Δ:

α = (1/n) · ||Δ||_1 = mean(|Δ_ij|)

Операция знака сводит каждую скалярную дельту к одному биту, плюс или минус. Масштабный коэффициент α возвращает величину. Вместе приближение сохраняет направленную информацию обновления дообучения при 1/32 стоимости хранения дельты полной точности (1 бит против 32 бит на элемент параметра).

Почему это работает? Дообучение большой предобученной модели даёт дельты, которые в среднем малы относительно базовых весов. Базовая модель схватывает господствующую структуру языка; дообучение схватывает поведенческие изменения поверх этой структуры. Когда дельта мала, знак каждого элемента — самое информативное представление этого элемента: величина примерно однородна по матрице дельты, поэтому α · sign(Δ) — эффективный код.

Это можно сказать строже. Если моделировать Δ_ij как случайную величину Лапласа с положением 0 и масштабом b:

Δ_ij ~ Laplace(0, b)
|E[sign(Δ) | |Δ|]| = 1 for all nonzero Δ

Знак полностью сохраняет направленную информацию. Потеря информации только в разбросе величины, который единственный коэффициент α частично восстанавливает. Для дельт дообучения, где разброс величины мал (изменение поведения, а не перенос знания), приближение плотное.

Сокращение памяти: у модели 70B в BF16 базовые веса занимают 140GB. Дельта каждого дообученного варианта в полном BF16 добавила бы ещё 140GB. В формате BitDelta каждая дельта занимает примерно 8.75GB (70B бит = 8.75GB, плюс пренебрежимо малые скаляры α). Кластер из 14 вариантов переходит от 14 × 140GB = 1.96TB к 140GB + 14 × 8.75GB ≈ 262GB — сокращение на 87%.

Архитектура обслуживания

┌─────────────────────────────────────────────────────────────────┐
│                    GPU CLUSTER (8x H100, 80GB each)             │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │              BASE WEIGHTS (140GB, HBM resident)          │   │
│  │  W_base for zen4-70b: attention, FFN, embeddings, norms  │   │
│  └──────────────────────────────┬───────────────────────────┘   │
│                                 │ shared read                    │
│  ┌──────────────────────────────▼───────────────────────────┐   │
│  │           DELTA CACHE (≈8.75GB per variant)              │   │
│  │  zen4-coder:    α_coder,    sign(Δ_coder)    [8.75GB]    │   │
│  │  zen4-ultra:    α_ultra,    sign(Δ_ultra)    [8.75GB]    │   │
│  │  zen4-math:     α_math,     sign(Δ_math)     [8.75GB]    │   │
│  │  zen4-writer:   α_writer,   sign(Δ_writer)   [8.75GB]    │   │
│  │  zen4-legal:    α_legal,    sign(Δ_legal)    [8.75GB]    │   │
│  │  zen4-medical:  α_medical,  sign(Δ_medical)  [8.75GB]    │   │
│  │  ...14 total...                                           │   │
│  └──────────────────────────────┬───────────────────────────┘   │
│                                 │                                │
│  ┌──────────────────────────────▼───────────────────────────┐   │
│  │              REQUEST ROUTER                               │   │
│  │  model_id → delta_load → reconstruct W_ft → inference    │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

В момент инференса конвейер обслуживания таков:

  1. Запрос приходит с model: "zen4-coder"
  2. Маршрутизатор находит запись дельта-кэша для zen4-coder
  3. Эффективный вес восстанавливается на лету: W_eff = W_base + α_coder · sign(Δ_coder)
  4. Прямой проход выполняется с восстановленными весами
  5. Ответ возвращается

Шаг 3 — критический путь. Восстановление — это слитое ядро CUDA, которое одновременно читает базовые веса и биты дельты и собирает эффективный вес в регистрах или разделяемой памяти, не материализуя полный W_eff в HBM. Восстановление и матричное умножение можно слить в один проход ядра: восстановленный вес никогда не существует целиком как независимый тензор.

# Simplified reconstruction kernel (conceptual)
def fused_bitdelta_linear(
    x: torch.Tensor,          # input activations [batch, seq, d_in]
    w_base: torch.Tensor,     # base weight [d_out, d_in] in HBM
    delta_sign: torch.Tensor, # 1-bit delta [d_out, d_in] packed as int8
    alpha: torch.Tensor,      # scale factor [d_out] or scalar
) -> torch.Tensor:
    # In practice: single CUDA kernel, no intermediate w_eff allocation
    # Conceptually equivalent to:
    delta_unpacked = unpack_bits(delta_sign).to(w_base.dtype)  # {-1, +1}
    w_eff = w_base + alpha.unsqueeze(1) * delta_unpacked
    return torch.nn.functional.linear(x, w_eff)

Подход со слитым ядром держит пиковое потребление пропускной способности HBM примерно на 1.5x весов базовой модели, а не на 2x, потому что дельта читается в упакованном битовом формате и распаковывается в быстрой SRAM во время слитой операции.

Профиль задержки

На практике в кластере обслуживания Zen из 14 вариантов:

ВариантP50 TTFTP99 TTFTк базовой линии
zen4 (base)18ms42ms1.0x
zen4-coder21ms48ms1.17x
zen4-ultra22ms51ms1.22x
zen4-math21ms47ms1.17x
zen4-writer20ms45ms1.11x

Накладные расходы времени до первого токена из-за восстановления BitDelta составляют 2-4ms — это мало относительно стоимости предзаполнения для типичных длин запроса. Ядро восстановления ограничено вычислениями, а не памятью, поэтому хорошо масштабируется вместе с генерацией на GPU.

Пропускная способность генерации (токенов в секунду) одинакова у базовой модели и любого варианта BitDelta, поскольку восстановление происходит только на фазе предзаполнения. Когда кэш KV заполнен, авторегрессионный шаг генерации идёт по стандартным путям matmul.

Качество: что теряется при сжатии в 1 бит

Честный ответ: немного, и эта потеря тщательно ограничена.

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

Эмпирически, по моделям Zen на их целевых наборах задач:

МодельПолная точностьBitDeltaDelta
zen4-coder (HumanEval)72.4%71.1%-1.3%
zen4-math (MATH-500)68.9%67.8%-1.1%
zen4-writer (WritingBench)84.2%83.7%-0.5%
zen4-legal (LegalBench)61.3%60.1%-1.2%

Деградация в 1-1.3% на основных наборах задач — цена сокращения памяти на 87%. Для всех вариантов Zen этот компромисс приемлем: разница в качестве ниже порога заметного пользователю изменения поведения на типичных сценариях.

MonoSoup: SVD-дополнение одной контрольной точки

MonoSoup (arXiv:2602.09689) решает дополнительную задачу: если есть несколько дообученных контрольных точек одной базовой модели, как слить их в одну контрольную точку, не загружая все одновременно?

MonoSoup применяет SVD к вектору задачи (Δ = W_ft - W_base), чтобы выделить низкоранговые компоненты с наибольшей поведенческой информацией, а затем собирает их по контрольным точкам:

For checkpoints {W_1, W_2, ..., W_k}:
  Δ_i = W_i - W_base
  [U_i, S_i, Vh_i] = truncated_SVD(Δ_i, rank=r)
  τ_i = U_i · diag(S_i) · Vh_i  # rank-r approximation

MonoSoup merge:
  W_merged = W_base + (1/k) · Σ τ_i

Усечение SVD на ранге r работает как шаг шумоподавления: дообучение обычно меняет модель вдоль малого числа направлений с высокой дисперсией в пространстве весов. Захват этих направлений усечённым SVD и их слияние берёт поведенческую суть каждой контрольной точки без шума направлений с меньшими сингулярными значениями.

Мы используем MonoSoup в прогонах обучения Zen как шаг усреднения контрольных точек: каждые 1000 шагов градиента сливаем текущую контрольную точку с прежним лучшим результатом через MonoSoup, а не наивным усреднением. Это снижает шум обучения и даёт более гладкие кривые потерь при лучшем итоговом качестве.

K-Merge: онлайн-управление адаптерами LoRA для края

K-Merge (arXiv:2510.13537) решает другой вид задачи обслуживания: на краю, где хранилище ограничено фиксированным бюджетом в B гигабайт, как эффективно обслуживать K адаптеров LoRA?

K-Merge держит пул слитых адаптеров при ограничении: суммарное хранилище никогда не превышает B. Когда запрошен новый адаптер, которого нет в пуле:

  1. Считается попарное сходство нового адаптера со всеми членами пула (косинусное сходство по векторам задач)
  2. Находится самый похожий существующий адаптер
  3. Новый адаптер сливается с самым похожим существующим через OPCM (слияние ортогональной проекцией)
  4. Если качество слитого адаптера на обеих задачах выше порога, исходный вытесняется и сохраняется слитая версия

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

Для краевого развёртывания Zen (Hanzo Desktop, мобильные устройства) K-Merge даёт локально обслуживаемые семейства моделей, которые иначе превысили бы пределы хранилища устройства. Устройство с 16GB доступного хранилища моделей может обслуживать 8 различных поведенческих профилей, держа пул K-Merge, который по запросу сливает похожие адаптеры.

Подробности инфраструктуры

Формат хранения дельты: матрицы знака в 1 бит хранятся как упакованные тензоры int8 (8 бит на байт, так что слой на 70B параметров с весом 4096×4096 упаковывается в 4096×512 int8). Масштабные коэффициенты α хранятся как float32, по одному на выходное измерение, и составляют примерно 0.1% хранилища дельты.

Поведение холодного старта: когда дельта варианта не закэширована в SRAM GPU (например, вариант не запрашивали 10 минут), первый запрос платит штраф задержки загрузки дельты примерно 200ms для модели 70B — время переноса 8.75GB из NVMe в HBM на скорости последовательного чтения NVMe. После первого запроса дельта остаётся в дельта-кэше SRAM GPU, пока её не вытеснит политика LRU.

Политика вытеснения кэша: LRU с весом по частоте запросов. Варианты с высокой частотой запросов закреплены; варианты с малым трафиком можно вытеснять. На практике 6 ведущих вариантов по объёму трафика всегда находятся в кэше; оставшиеся 8 делят оставшееся место кэша с вытеснением LRU.

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

Итог: 14 вариантов моделей Zen обслуживаются с узлов 8x H100, для которых раньше потребовались бы узлы 112x H100 при той же возможности. Снижение стоимости инфраструктуры не является постепенным. BitDelta делает мультиарендное семейство моделей экономически состоятельным.


Модели Zen доступны на zenlm.org. Доступ к API на hanzo.ai.

Комментарии

Войдите с Hanzo ID, чтобы комментировать.

Загрузка комментариев…

Читать далее