В какой‑то момент я посмотрел на 13 ГБ весов LLaMA-7B и подумал: а почему, собственно, они должны занимать именно столько? Что, если не делать маленькую модель, не учить её заново и не заставлять копировать большую, а просто найти более короткую запись тех же «мозгов»?
Здравый человек, вероятно, скачал бы готовую квантизацию и пошёл заниматься своими делами. Я вместо этого поставил цель ужать модель сначала до 2 ГБ, а в идеале — до одного. Без дообучения, дистилляции, pruning и изменения архитектуры. Только математика над уже готовыми весами и небольшая выборка текстов для калибровки.
Так появился PCST — домашний исследовательский проект, который довольно быстро превратился в длинный сериал. В нём уже больше 60 проверенных методов, сотни конфигураций, несколько версий модели, один очень неприятный системный баг и приличная коллекция идей, которые выглядели прекрасно ровно до тех пор, пока я не запускал модель целиком.
Спойлер: одного гигабайта и качества Q8 я не получил. Текущий артефакт занимает 2.05 GiB и пока проигрывает обычной Q3_K_M и по качеству, и по скорости. Зато я наткнулся на вещь, которая оказалась интереснее очередной красивой цифры: можно заметно улучшить восстановление отдельных весов и одновременно сделать всю модель глупее.
Ниже — история о том, куда исчезают локальные улучшения, почему методы из картинок почти бесполезны для сырых весов и как одна ошибка с transpose заставила пересмотреть большую часть уже полученных результатов.
Зачем вообще пытаться засунуть 7B‑модель в пару гигабайт
Причина довольно приземлённая. Локальная модель не просит постоянного доступа к облаку, не отправляет документы во внешний API и продолжает работать в закрытой сети. Её можно поставить в лаборатории, небольшой компании или просто на компьютер, который не хочется превращать в терминал для чужого сервера.
И речь не только о LLaMA-7B. Если научиться воспроизводимо уменьшать модели в шесть‑семь раз, то условные 100 ГБ превратятся не в магические 2 ГБ, а в куда более реальные 14–17. Это всё ещё много, но такую модель уже можно представить на обычном компьютере с 32 ГБ памяти.
Но маленький файл сам по себе ничего не решает. Важны ещё качество, скорость и пиковая память. Если при каждом умножении полностью восстанавливать исходную матрицу в FP32, компактный артефакт снова потребует десятки гигабайт RAM. Поэтому долгосрочная задача PCST — не архиватор весов, а представление, из которого можно выполнять inference напрямую.
Я специально ограничил себе пространство для манёвра. Нельзя выбросить половину слоёв, уменьшить FFN или обучить ученика повторять учителя. Нельзя провести обычный gradient fine‑tuning Transformer‑блоков. Зато можно изучать активации, менять систему координат, искать низкоранговые компоненты, строить кодовые книги и решать, каким матрицам действительно нужны дополнительные биты.
Иными словами, вопрос звучит так: можно ли найти более короткую запись уже существующего вычисления?
Первый большой провал: некоторое время я сжимал не ту LLaMA
Самый болезненный день проекта наступил 21 августа 2026 года. В моём NumPy‑loader нашлась ошибка ориентации GGUF‑тензоров. Матрица уже приходила в форме (out, in), а я ещё раз перекладывал её через reshape там, где требовался transpose.
Самое неприятное в таких ошибках — программа не падает. Модель что‑то считает, графики ползут вверх или вниз, отдельные методы даже «побеждают». Только победы относятся к неправильно собранной сети. После исправления большую часть ранних результатов пришлось честно пометить как невалидную и начать заново.
Чтобы больше не строить выводы на неисправном основании, Q8-forward был проверен против независимой реализации llama.cpp на одинаковых токенах.
Проверка Q8 NumPy против llama.cpp | Результат |
|---|---|
Cosine logits | 0.999942 |
Relative error | 0.01140 |
Top-1 agreement | 100% |
Top-10 overlap | 97.5% |
С тех пор у проекта появилось простое правило: сначала докажи, что собственный декодер повторяет независимую реализацию, и только потом радуйся новым методам. Красивое улучшение внутри неверного forward — всего лишь очень убедительная ошибка.
Как PCST хранит веса, если сильно упростить
В основе PCST лежит Product Quantization. Берём матрицу, режем её на короткие векторы и вместо каждого вектора сохраняем номер похожего центроида из небольшой кодовой книги. Если совсем грубо: w_block ≈ C[index].
Размер при этом складывается не только из индексов. Нужно учитывать сами codebooks, scale‑факторы, низкоранговые остатки, embedding, LM Head и метаданные контейнера. Поэтому теоретическое количество бит на параметр легко выглядит лучше реального файла.
Довольно быстро выяснилось, что один рецепт для всей сети не работает. Матрицы Q, K, V, O, Gate, Up и Down по‑разному переживают одну и ту же степень сжатия. Более того, две одинаково устроенные матрицы на разных слоях могут вести себя совершенно по‑разному. Поэтому вопрос «сколько бит дать модели?» пришлось заменить другим: «куда потратить следующий мегабайт, чтобы он принёс больше всего пользы?»
Для проверки использовалась LLaMA-7B семейства Llama 1 в Q8_0 GGUF. Q8 служил эталоном logits, а стандартные GGUF‑квантизации запускались через llama.cpp. Финальный внешний прогон выполнялся на 32 независимо сбрасываемых документах WikiText-2 validation — всего 6 688 предсказываемых позиций.
Сначала очень хотелось выбрать одну красивую метрику и ориентироваться только на неё. Не получилось. Perplexity говорит, насколько модель угадывает настоящий следующий токен, но не показывает сходство с Q8. Cosine logits может выглядеть прилично, когда порядок ближайших слов уже заметно изменился. В итоге пришлось одновременно следить за NLL, perplexity, Top-1, Top-10, KL, MassRecall, размером, скоростью и памятью.
Что всё‑таки сработало
Первой полезной находкой стала неоднородность слоёв. Равномерно раздать всем одинаковое количество бит звучит справедливо, но для модели это плохая экономика. Дополнительный бюджет для некоторых ранних V/O приносил гораздо больше, чем те же байты в другой части сети. Так появилась selective policy: каждой матрице — свой диагноз и свой бюджет.
С нормализацией произошла похожая история. Column‑RMS очень хорошо помог attention O, а внешне похожий Row‑RMS для V и FFN после исправления бага делал только хуже. На нулевом слое ошибка O упала с 0.6791 до 0.1619, а cosine вырос с 0.9283 до 0.9920. Вывод немного комичный: совет «нормализуйте веса» примерно так же полезен, как совет «оптимизируйте код». Важно чем, где и по какой оси.
Следом я попробовал чинить остаток после PQ через низкий ранг: W ≈ W_PQ + UΣVᵀ. Интуитивно хочется прикрутить такую поправку везде, но почти везде она только ела место. Для FFN Down residual окупился на слоях 0, 1, 2, 4, 30 и 31, а для V/O — на 24, 27 и 31. В итоге он стал не универсальным улучшателем, а чем‑то вроде точечного ремонта особенно капризных матриц.
Иногда помогал и поворот системы координат. Согласованное ортогональное преобразование связанных весов не меняет функцию точной модели, зато может сделать числа удобнее для квантизатора. Gauge Hadamard принёс пользу для V/O на L5 и L8. Попытка размазать успех по всей сети снова не прошла: другие слои и seeds решили жить по своим правилам.
Небольшой, зато повторяемый выигрыш дала последовательная калибровка. Настраивать десятый слой на идеальном выходе первых девяти — всё равно что готовить подвеску автомобиля для дороги без единой кочки. В PCST на вход уже приходит результат предыдущих сжатых блоков. Когда калибровка начала использовать именно эти реальные prefix states, модель стала вести себя немного лучше.
Embedding и LM Head также пришлось рассматривать отдельно. Для embedding приемлемым оказался Q4 group-32. LM Head удалось уменьшить примерно со 132.8 до 85.9 MiB через Q5 group-32 с высоким локальным cosine. Более агрессивное PQ‑кодирование головы сохраняло вполне приличную ошибку восстановления, но резко ухудшало ranking токенов.
Лучшая находка ждала не внутри слоя, а почти у самого выхода
После 32 сжатых блоков hidden states PCST оказываются в слегка изменившейся системе координат. А LM Head по‑прежнему читает их так, будто перед ним аккуратный выход Q8. Представьте карту, которую немного повернули, но забыли предупредить навигатор.
Отсюда появилась гипотеза: вдруг часть накопленной ошибки — не случайный шум, а общий перекос, который можно поправить перед самой головой?
Я построил low‑rank bridge между PCST hidden states и соответствующими Q8 hidden states, а затем свернул его непосредственно в Q5 LM Head. Новый runtime‑оператор и дополнительный payload не потребовались. Конфигурация rank=32, alpha=0.7 была выбрана на development‑наборе и один раз проверена на закрытом test‑наборе.
Метрика | Q5 baseline | Q5 + bridge | Изменение |
|---|---|---|---|
Top-1 agreement | 65.025% | 65.809% | +0.784 п.п. |
Top-10 overlap | 62.917% | 64.623% | +1.706 п.п. |
MassRecall@10 | 71.813% | 72.256% | +0.443 п.п. |
KL | 0.54776 | 0.49419 | −0.05358 |
Cosine logits | 0.85381 | 0.88232 | +0.02850 |
Это оказалось самым сильным подтверждённым улучшением текущей версии. Для меня важнее даже не сами полтора‑два процентных пункта. Bridge показал, что сеть портится не только случайно: в накопленной ошибке есть структура, которую можно найти и частично исправить одним небольшим отображением.
А теперь о кладбище красивых идей
Долгое время план казался очевидным: выиграть процент на V, ещё процент на O, затем немного улучшить Gate, Up и Down — и в конце собрать все проценты в хорошую модель. К сожалению, Transformer отказался складывать мои улучшения по правилам школьной арифметики.
Residual VQ, additive quantization, Hessian‑aware центроиды и совместная оптимизация связанных матриц часто честно уменьшали MSE весов или ошибку конкретного блока. Затем я запускал всю модель — и Top-10 падал. Следующие слои усиливали именно те направления ошибки, которые локальная метрика считала дешёвыми.
Самый наглядный случай — alternating joint V/O на L16. Я оптимизировал локальное произведение A·V·O, не добавляя ни байта payload. Внутри блока всё выглядело разумно. На независимом наборе Top-10 рухнул с 62.42% до 56.61%, cosine — с 0.8620 до 0.8130, а KL вырос с 0.6174 до 0.8970. Реконструкция стала красивее, язык — хуже.
Попытка напрямую оптимизировать ranking тоже не дала лёгкого выхода. Top‑K — дискретная и высокодисперсионная цель. Победы на десятках или сотнях позиций исчезали на новых документах. Даже margin‑subspace correction, обученный на 16 320 позициях и проверенный на 4 080 development‑позициях, уменьшил Top-10 с 62.42% до 60.76% и ухудшил NLL.
Отдельная ветка экспериментов выросла из вопроса: если мы умеем фантастически сжимать картинки и видео, почему бы не посмотреть на матрицу как на изображение? Ответ: потому что это не изображение. Wavelets, quadtree, Gaussian splatting, фракталы и координатные полиномы рассчитывают на гладкость и соседство. А два числа рядом в файле весов не обязаны иметь хоть что‑то общее. Для V нулевого слоя корреляция соседних координат была около нуля, а RMS разности — примерно 1.414× RMS самого веса, почти как у некоррелированного сигнала.
Межслоевое delta‑кодирование, общие FFN codebooks и общие V/O subspaces тоже не дали ожидаемой экономии. Два соседних слоя могут выполнять похожую абстрактную роль, но это не означает совпадения их внутренних координат.
Наконец, lossless‑сжатие индексов почти исчерпало себя. Их энтропия близка к используемой битовой ширине, а идеальная оставшаяся экономия оценивается примерно в 17.5 MiB. Этого недостаточно, чтобы закрыть сотни мегабайт между текущей точкой и экстремальной целью.
Я не считаю эти результаты опровержением целых научных направлений. В частности, локальные AQLM‑, QTIP‑ и VPTQ‑подобные пилоты не заменяют полноценные реализации соответствующих методов. Они закрывают только конкретные рецепты, бюджеты и ограничения текущего PCST runtime.
Так что получилось в реальности
Финальный материализованный артефакт llama-7b-pcst-v5-quality.pcstc занимает 2.05 GiB. Он содержит все 32 Transformer‑слоя, PQ‑потоки, выбранные SVD residual, gauge‑преобразования, Q8 embedding и Q5 LM Head со встроенным bridge.
Контейнер проходит lossless round‑trip индексов, точно соответствует исходной policy и детерминированно воспроизводит hidden states. То есть файл настоящий, не оценка в Excel. Но корректно собранные два гигабайта ещё не означают хорошую модель — поэтому дальше идёт самая неприятная таблица этой статьи.
Вариант | Размер | PPL ↓ | Top-10 к Q8 ↑ | Target Top-1 ↑ | Скорость* | Peak RSS* |
|---|---|---|---|---|---|---|
Q8_0 | 6.67 GiB | 6.922 | 100.00% | 57.13% | 20.33 tok/s | 13.80 GiB |
PCST v5 | 2.05 GiB | 15.560 | 59.59% | 43.56% | 2.43 tok/s | 4.26 GiB |
Q3_K_M | 3.07 GiB | 7.356 | 86.93% | 56.25% | 18.64 tok/s | 6.12 GiB |
Q4_K_M | 3.80 GiB | 7.009 | 92.65% | 56.89% | 30.36 tok/s | 7.13 GiB |
* PCST работает через исследовательский Python‑декодер, а GGUF — через оптимизированный llama.cpp. Такое сравнение показывает состояние реализаций, но не теоретический предел форматов.
PCST v5 примерно в 3.25 раза меньше Q8 и на 1.02 GiB меньше Q3_K_M. Но perplexity в 2.12 раза выше, чем у Q3_K_M, а Python‑декодер намного медленнее. Поэтому нет, это пока не «убийца Q3», не качество Q6 и не готовая модель для Raspberry Pi.
По отношению к исходным FP16-весам размер уменьшился примерно в шесть раз. Но практический успех проекта требует не только коэффициента сжатия: необходимо значительно лучше сохранить способности модели и выполнять операции непосредственно из компактного представления.
Где, кажется, спрятана настоящая задача
В начале я думал, что где‑то существует ещё один удачный compressor для матрицы — нужно только перевернуть правильный камень. После десятков попыток гипотеза изменилась. Возможно, мы вообще выбрали неверный объект сжатия. Сохранять нужно не отдельные таблицы чисел, а поведение связанной сети.
Ошибка проходит через residual stream, меняет статистику следующих активаций и взаимодействует с нелинейностями. Поэтому два приближения с одинаковой MSE могут давать совершенно разный результат на logits. И наоборот, неидеальное восстановление весов может быть приемлемым, если ошибка лежит в направлениях, которыми реальные активации почти не пользуются.
Теперь я хочу измерять причинную ценность каждого мегабайта. Идея простая: временно вернуть одну матрицу в Q8 внутри полной PCST‑модели и посмотреть, насколько изменится конечный NLL. Такой counterfactual audit должен отделить реальные узкие места от матриц, которые страшно выглядят по MSE, но почти не влияют на язык.
Параллельно нужен честный equal‑size baseline против IQ2_XXS, IQ2_XS, IQ2_S, TQ2_0 и Q2_K. Если стандартный i‑quant окажется сильнее bulk‑PQ, PCST может превратиться из самостоятельного кодировщика в глобальный оптимизатор над стандартным форматом: распределять биты по sensitivity map, добавлять редкие residual и исправлять систематическую выходную ошибку через bridge.
После появления конкурентного представления потребуется нативный runtime: mmap‑контейнер, fused lookup‑and‑accumulate kernels, SIMD для x86 и ARM и умножение без восстановления полной FP32-матрицы. Пока Python‑прототип нужен для исследования правильной математики, а не для доказательства скорости.
Что я вынес из этой истории
Я не получил 1–1.5 GiB с качеством, близким к Q8. PCST v5 не победил стандартные квантизации. Можно было бы оставить только удачные графики, но тогда от проекта осталось бы гораздо меньше пользы.
Зато теперь точно известно, что LLaMA-7B можно технически упаковать в 2.05 GiB и запустить без дообучения. Появилась карта капризных слоёв, несколько действительно работающих поправок и длинный список способов сделать матрицу «лучше», одновременно испортив модель.
Самым важным результатом для меня стал новый вопрос:
Как кодировать вычисление всей сети так, чтобы ошибки отдельных представлений компенсировали друг друга на реальном manifold активаций?
Ответа у меня пока нет. Но теперь хотя бы понятно, где его искать, а новые идеи можно проверять на валидированном forward, независимых документах, закрытом test‑наборе и нормальных baseline — без гадания по одной красивой MSE.
Исследование продолжается. Если вы занимались training‑free network‑wide compression, cross‑layer error shaping или писали нативный inference для векторно‑квантованных весов — приходите спорить. Особенно интересно узнать, под каким камнем я ещё не посмотрел и какой из уже закрытых методов стоит проверить заново в другой комбинации.
Полный англоязычный препринт с постоянным DOI опубликован на Zenodo. Английская веб‑версия доступна на DEV Community.
Основные связанные работы
Frantar et al. GPTQ: Accurate Post‑Training Quantization for Generative Pre‑trained Transformers.
Chee et al. QuIP: 2-Bit Quantization of Large Language Models With Guarantees.
Egiazarian et al. Extreme Compression of Large Language Models via Additive Quantization.
Liu et al. VPTQ: Extreme Low‑bit Vector Post‑Training Quantization for Large Language Models.
Saha et al. Compressing Large Language Models using Low Rank and Low Precision Decomposition.
ggml‑org. llama.cpp Tensor Encoding Schemes.

