Як Preply скоротила витрати на LLM на 46% до масштабування AI-фіч

💡 Усі статті, обговорення, новини про AI — в одному місці. Приєднуйтесь до AI спільноти!

«А ми взагалі можемо собі дозволити це масштабувати?»

Це питання неминуче виникає одразу після «Воно взагалі працює?», коли будуєш на LLM. Продукт стабільний. Якість є. Користувачі задоволені. Ти прораховуєш витрати при 10x, 50x, 100x від поточних обсягів — і починаєш шукати, де можна оптимізуватися, ще до того як рости.

Саме це сталося і в нас у Preply. AI-фічі показували хороші результати, і поки ми готувалися масштабувати їх із режиму вдалого експерименту в production-ready систему, у нас з’явилася можливість переосмислити підхід до використання LLM. Не для того, щоб зрізати кути чи жертвувати якістю, а щоб думати розумніше про те, куди йде кожен токен.

Ось 8 vendor-agnostic оптимізацій, які зробили різницю. Деякі — швидкі wins, реалізовані за день. Інші вимагали серйознішої інженерної роботи. Разом вони зробили перехід від «це працює» до «це стійко» реально можливим.

З чого складається вартість LLM

Перш ніж оптимізувати, треба зрозуміти, що формує рахунок.

LLM-и беруть плату за токени — невеликі шматки тексту, що обробляються моделлю. Основні типи токенів, які ти побачиш у своїх витратах:

  • Input tokens — все, що ти надсилаєш (промпти, інструкції, контекст)
  • Output tokens — те, що генерує модель (зазвичай дорожче)
  • Reasoning tokens — використовуються деякими просунутими моделями для внутрішніх кроків міркування. У фінальній відповіді їх не видно, але платити за них доводиться
  • Cached tokens — раніше оброблені префікси, які можна повторно використати

Кожен тип токенів по-різному відображається у рахунку, тому моніторинг допомагає розібратися, що саме тягне витрати вгору.

На практиці зниження витрат на LLM зводиться до двох напрямків: обмежити кількість токенів, які модель має обробляти, і налаштувати конфігурацію моделі для ефективнішої роботи. Більшість оптимізацій підпадає під одну з цих двох категорій. Ось основні стратегії, що спрацювали у нас.

У статті наводяться конкретні підрахунки токенів для ілюстрації економії. Вимірювання зроблені за допомогою токенайзера GPT-4 від OpenAI [1]. Кількість токенів може дещо відрізнятися залежно від моделі та токенайзера, але принципи оптимізації та відносна економія залишаються стабільними незалежно від провайдера.

1. Переходь на новіші, ефективніші моделі

Нові моделі виходять часто — нерідко з кращою продуктивністю, а менші моделі зазвичай коштують дешевше. Тому варто регулярно переглядати, яка модель обслуговує кожен конкретний промпт — можливо, новіший або компактніший варіант впорається не гірше.

Ми одразу побачили оптимізацію для кількох промптів, мігрувавши з моделей попереднього покоління на зразок GPT-4o на новіші — GPT-5-mini та Gemini Flash 2.5. Планка якості відрізнялася, але завдяки наявним evals ми змогли визначити, де бракувало додаткових інструкцій і потрібне доопрацювання. Наш підхід до реалізації:

  • Запускаємо evals: порівнюємо поточну модель із новішими
  • Вимірюємо деградацію якості
  • Там, де потрібно, додаємо інструкції або легке тюнінгування
  • Перемикаємось там, де поріг якості досягнуто, через A/B-тест

На практиці ця одна зміна може дати величезний результат і часто приносить найбільшу миттєву економію.

2. Структуруй промпти для кешування

Кешування промптів може скоротити витрати на повторні input-токени до 90% залежно від LLM-провайдера — але тільки якщо правильно структурувати промпти.

Правило просте: статичний контент має йти першим. Коли промпт починається з незмінних інструкцій або контексту, модель кешує ці токени і обробляє тільки нове.

Візуалізація кешування промптів, яка показує влучання та промахи кешу

Якщо динамічний контент з’являється на початку, кожен запит інвалідує кеш і ти платиш повну ціну.

У нашому випадку це більше було перевіркою безпеки: більшість production-промптів вже використовували цей патерн. Але аудит структури промптів допоміг виловити кілька крайніх випадків, де кешування не працювало так, як очікувалося, — і підняти cache hit rate на кілька відсоткових пунктів.

3. Прибирай невикористовувані поля з відповідей

При швидких ітераціях поля у відповідях накопичуються. Одні допомагали з дебагінгом. Інші корегували поведінку моделі. Багато з них у production більше не потрібні.

Ми виявили це, аудитуючи scoring-промпт. Відповідь містила поле score (яке застосунок використовував) і поле reasoning (яке ні). Видалення зайвого поля reasoning скоротило відповідь з 40 токенів до 9.

Цей патерн виявився поширеним. Аудитуючи інші production-промпти, ми знайшли ще кілька випадків, де поля, додані для дебагінгу, експериментів або вже задепрекейчені, продовжували генеруватися без жодної практичної мети. Запуск evals після видалення цих полів підтвердив підозри: якість залишилась незмінною, а витрати суттєво впали.

Висновок простий. Регулярно переглядай схеми відповідей і видаляй поля, які насправді не використовуються в production.

4. Скорочуй назви полів

Ще одна оптимізація — скорочення назв полів у виводі. Особливо корисно при отриманні великих списків, де однакові ключі повторюються багато разів.

Навіть при ідентичному контенті коротші назви дають невелике, але відчутне зниження витрат. На масштабі ця економія стає помітнішою.

У цьому прикладі з фідбеком для мовленнєвої практики скорочення назв полів зменшує відповідь з 131 токена до 99, тобто на 24%. Це простий кейс лише з двома utterances. У production, де відповіді містять десятки вкладених елементів, та ж оптимізація може кардинально змінити витрати на output-токени.

5. Кодуй повторювані константи

Категорії, теги й enum-значення, що часто зустрічаються у відповідях, можна стиснути.

Замість того щоб виводити довгі назви категорій на зразок PRESENT_PERFECT_CONTINUOUS або SUBJECT_VERB_AGREEMENT десятки разів, їх можна замінити короткими кодами — наприклад, PPC або SVA — і розшифровувати їх у людиночитабельний формат вже на рівні застосунку.

У цьому прикладі кодування граматичних категорій скорочує відповідь з 50 токенів до 17, тобто на 66%. Чим довші значення констант і чим частіше вони повторюються, тим драматичніша економія.

Ця оптимізація стояла в кінці нашого списку, бо її правильна реалізація вимагає серйознішого prompt engineering: треба включати в промпт пояснення маппінгу, що своєю чергою додає токени до input-витрат.

6. Використовуй ефективніші формати для структурованих даних

JSON зрозумілий людині, але ненажерливий до токенів. Кожна дужка, лапка і кома коштують грошей. Для деяких промптів, де це принципово важливо, ми відмовилися від стандартного JSON на користь компактніших форматів.

Різниця може бути суттєвою. Як видно з прикладу нижче, та сама інформація у JSON займає 72 токени, тоді як у компактнішому CSV-форматі — лише 29, тобто на 60% менше. Є й інші альтернативи: XML, TOON [3] та інші формати для експериментів.

Економія накопичується швидко, коли очікуються довгі структуровані відповіді з десятками або сотнями елементів. Компроміс — складність парсингу і надійність. JSON — найнадійніший формат, адже моделі навчені на ньому найбільше. Альтернативні формати можуть вимагати ретельнішого prompt engineering і обробки помилок. У нашому випадку CSV добре спрацював для конкретної задачі, але перед виходом у production довелося ретельно протестувати і виміряти надійність парсингу.

⚠️ Застереження: Ця оптимізація є експериментальною. Хоча вона добре спрацювала в нашому конкретному кейсі, рекомендуємо ретельну валідацію та оцінювання перед більш широким застосуванням. Результати можуть відрізнятися залежно від промптів, моделей і складності виводу.

7. Використовуй Batch API для завдань не в реальному часі

Для задач, що можуть почекати, batch API [4, 5, 6] пропонує ту саму якість за значно нижчою ціною. Ідея проста: замість того щоб відправляти запити по одному і чекати миттєвих відповідей, ти подаєш пакет запитів і отримуєш результати пізніше. Більшість провайдерів дають знижку близько 50% на batch-обробку, а разом із тим — вищі rate limits, адже вони можуть оптимізувати розклад виконання.

Головне — визначити, які workloads можуть терпіти затримку. Задачі на зразок evals або обробки історичних даних — ідеальні кандидати. Перенесення batch-eligible роботи на Batch API — максимально прямолінійне рішення. Ті самі моделі, той самий вивід, просто відкладена доставка за половину ціни.

8. Об’єднуй кілька вхідних даних в один запит

Ще одна оптимізація — обробляти кілька вхідних даних в одному API-виклику замість окремих запитів. Економія виникає за рахунок усунення повторюваних токенів інструкцій.

Коли обробляєш елементи по одному, кожен запит включає повний системний промпт і інструкції. 200 токенів інструкцій, відправлених 10 разів — це 2000 токенів, які можна батчити в один запит. Ось звідки береться економія.

У цьому прикладі три окремі запити на класифікацію використовують загалом 184 токени. Батчинг в один запит скорочує це до 129 токенів, тобто на 30%, завдяки усуненню дублювання інструкцій.

Валідація якості тут обов’язкова. Іноді спільна обробка елементів впливає на точність і робить систему більш схильною до галюцинацій. До того ж батчинг збільшує час обробки одного запиту, бо модель має обробити більше контенту і згенерувати більшу відповідь, — хоча ти і заощаджуєш на overhead від множинних API-викликів.

Результати і ключові висновки

Ми не оптимізували все одночасно. Проаналізувавши використання API за місяць, ми визначили топ-15 промптів за обсягом токенів і оптимізували їх один за одним. Кожна оптимізація запускалася як A/B-тест, щоб вчасно виловлювати будь-яку деградацію якості. Таким підходом ми досягли 46% зниження загальних витрат на LLM, зробивши AI-фічі готовими до масштабування.

Не кожна оптимізація підходить для кожного кейсу. Кожна стратегія несе компроміси, які потребують ретельного обдумування. Перехід на інші моделі вимагає глибокої валідації якості через evals. Скорочення назв полів означає оновлення промптів і повторний запуск оцінок. Альтернативні формати виводу вносять складність парсингу і потенційні проблеми з надійністю. Батчинг знижує витрати, але збільшує latency. Головне — вибирати оптимізації, що відповідають твоїм конкретним обмеженням: чи то скорочення витрат, чи engineering bandwidth, чи вимоги до latency, чи порогові значення якості.

Ключові висновки:

  • Починай із переходу на менші моделі — найбільший миттєвий ефект при мінімальних інженерних зусиллях
  • Структуруй промпти для кешування — до 90% знижки на повторювані input-токени — один із найлегших wins
  • Регулярно переглядай схеми виводу — невикористовувані поля накопичуються і тягнуть витрати вгору
  • Скорочуй назви полів у списках — невелика економія на елемент множиться з обсягом
  • Кодуй довгі повторювані константи — обмінюй input-токени на скорочення output-токенів
  • Оптимізуй формати — невеликий відсоток економії множиться на масштабі
  • Батч для workloads, що можуть почекати — близько 50% знижки для відкладеної обробки

Закладай cost awareness у процес розробки — лови проблеми рано, до того як вони масштабуються

👍ПодобаєтьсяСподобалось19
До обраногоВ обраному10
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Святославе, дякую за статтю.
Щодо прибирання поля reasoning з output, чи є у вас промпти з яких було оправдано не прибирати це поле навіть якщо логіка програми його не використовує? Наприклад, якщо у вас логіка промпта прив’язана до output поля reasoning і є конструкції щось типу значення поля X має узгоджуватися з полем reasoning?
Чи є у вас промпти результат яких можна оцінити статистично? Наприклад, парсинг інформації з документів/картинок. Якщо є і якщо з них прибирали поле reasoning, чи оцінювали зміну результатів статистично?

Олексію, дякую за коментар!
У нашому випадку виключень не було, логіка не була зав’язана на полі reasoning, тому його можна було безпечно прибрати. Там, де модель дійсно потребувала reasoning ми підбирали відповідні параметри LLM.
Щодо статистичної оцінки, так для всіх промптів ми робимо оцінку якості, зокрема і для задач парсингу. Для кожного промпта формуємо набір даних і підбираємо метрики, що найкраще відображають якість конкретної задачі (як стандартні метрики з досліджень, так і кастомні під задачу). На додаток до цього нові зміни, у тому числі й прибирання reasoning, релізимо через A/B тести й порівнюємо метрики до і після.

Там є ще такий нюанс що моделі обучали на маркдаунах, на звичайній мові, і це те, що вони найкраще розуміють. Навіть банальну таблицю, яка жре багато токенів, вони розуміють краще ніж звичайний список, тому що люди використовують таблиці. А xml полюбляють більше ніж json.

Тобто скорочуючи токени різними техніками, використовуючи нестандартні чи менш поширені формати, економить токени, но робить модель трохи тупішою, що може призвести до кривого результата і зайвих запитів в ЛЛМ. Плюс більше токенів на reasoning.

Але якщо тести проходять, то норм. І взагалі норм контент, правильне використання і оптимізація ЛЛМок.

Ще кілька технік, але це вже більше агентів стосується:

1. Все що можна робити в коді, робити в коді. Агентам давати лише недетерміновані штуки і reasoning.

2. Контекст по-можливості підготувати по-максимуму, щоб агент сам не ходив і не шукав, а то поки знайде то контексту не залишиться для reasoning-a.

3. Розбивати на дрібні задачі з чистим контекстом, де попередній агент подготовлю контекст для наступного, тоді можна буде використовувати моделі по-дешевше. Ну і чим короча сесія, тим вона дешевша. Хоча, не завжди, дивлячись як кеш використовується.

4. Забути про ці всі розрекламовані ютюберами рої агентів які між собою спілкуються і працюють над великою задачею. Оркестрація має бути в коді, і код знає якого агента коли викликати і який контекст йому дати.

0. Не використовуйте LLM, якщо завдання можна виконати алгоритмічно.

Феншуй!

Підписатись на коментарі