Снижение задержки (Latency) и метрика Time-to-First-Token

35 минут Урок 32

Урок: Снижение задержки (Latency) и метрика Time-to-First-Token

Добро пожаловать в седьмой модуль курса по Gemini 3 API. Мы переходим от функциональной разработки к тому, что отличает «домашний» прототип от высоконагруженного сервиса в продакшене — к инженерной оптимизации производительности.

В мире генеративного ИИ скорость — это не просто техническая характеристика, это фундаментальная часть пользовательского опыта (UX). Пользователи привыкли к мгновенной реакции интерфейсов. Когда ваш AI-агент «думает» 10 секунд перед ответом, магия разрушается, а пользователь начинает сомневаться в работоспособности системы.

В этом уроке мы глубоко погрузимся в анатомию сетевых запросов к LLM, разберем критически важные метрики Latency и TTFT, и научимся их оптимизировать, используя архитектурные особенности Gemini 3.

Анатомия задержки: Что происходит, когда вы нажимаете «Отправить»?

Чтобы бороться с задержкой, врага нужно знать в лицо. Давайте деконструируем жизненный цикл запросу к LLM. Общее время ожидания (Total Latency) складывается из нескольких этапов:

  1. Network RTT (Round Trip Time): Время, за которое ваш запрос долетает от клиента (или вашего бэкенда) до серверов Google и возвращается обратно.
  2. Queueing (Очередь): Время ожидания свободного слота GPU/TPU в кластере провайдера. В моменты пиковой нагрузки это может стать узким местом.
  3. Prefill (Обработка контекста): Модель должна «прочитать» и токенизировать весь ваш промпт, системные инструкции и историю чата. Это фаза чтения. Она параллелится, но зависит от длины входных данных.
  4. Decoding (Генерация): Фаза написания ответа. Это последовательный процесс: модель генерирует токен за токеном. Этот этап самый медленный, так как каждый следующий токен зависит от предыдущего.

Понимание этой структуры помогает выбрать правильную стратегию оптимизации. Если у вас медленный старт — проблема в Prefill. Если медленно пишется текст — проблема в Decoding.

Король метрик: Time-to-First-Token (TTFT)

В традиционных веб-сервисах мы смотрим на общее время ответа. В стриминговых AI-приложениях эта метрика вторична. Главный герой здесь — TTFT.

Time-to-First-Token (TTFT) — это время, прошедшее с момента отправки запроса до момента, когда пользователь увидел на экране первый символ ответа.

Почему это так важно?

  • Психология восприятия: Исследования UX показывают, что задержка до 200-400 мс воспринимается как мгновенная. Если пользователь видит, что текст начал печататься через 0.5 секунды, он готов ждать окончания генерации хоть 10 секунд, потому что видит прогресс.
  • Ощущение «живого» диалога: Низкий TTFT создает иллюзию, что собеседник слушает и отвечает сразу, а не уходит в библиотеку за ответом.

Вторая важная метрика — Time-Between-Tokens (TBT) или скорость генерации (токены в секунду). Она определяет, насколько быстро «бежит» текст. Для комфортного чтения скорость должна быть выше скорости чтения человека (примерно 5-10 токенов/сек).

python
import time
import google.genai as genai

# Настройка клиента (предполагаем, что API_KEY уже в окружении)
client = genai.Client()

def measure_performance(prompt, model_id='gemini-3-flash'):
    print(f"--- Тестирование модели: {model_id} ---")
    
    start_time = time.time()
    ttft = 0
    token_count = 0
    first_token_received = False
    
    # Используем стриминг для измерения TTFT
    response = client.models.generate_content_stream(
        model=model_id,
        contents=prompt
    )
    
    for chunk in response:
        if not first_token_received:
            ttft = time.time() - start_time
            first_token_received = True
            print(f"⚡ TTFT (Time-to-First-Token): {ttft:.4f} сек.")
        
        # В реальном коде здесь была бы логика обработки токенов
        if chunk.text:
             token_count += 1 # Упрощенный подсчет чанков как токенов для примера
            
    total_time = time.time() - start_time
    print(f"⏱️ Общее время: {total_time:.4f} сек.")
    
    # Расчет средней скорости генерации (исключая время на prefill/TTFT)
    generation_time = total_time - ttft
    if generation_time > 0:
        tps = token_count / generation_time
        print(f"🚀 Скорость генерации: ~{tps:.2f} chunks/sec")

measure_performance("Напиши короткое стихотворение про скорость света.")

Стратегия 1: Выбор модели (Flash vs Pro vs Ultra)

Самый простой рычаг управления задержкой — выбор правильной модели. Семейство Gemini 3 спроектировано с учетом градации скорости и интеллекта.

  • Gemini 3 Flash: Чемпион по низкому TTFT. Оптимизирована для высокой пропускной способности и мгновенных ответов. Идеальна для чат-ботов, простых задач классификации и извлечения данных.
  • Gemini 3 Pro: Баланс. Используйте, когда нужны сложные рассуждения, но время ответа всё ещё критично.
  • Gemini 3 Ultra: Тяжелая артиллерия. Самая высокая задержка. Используйте для оффлайн-задач, сложной аналитики или там, где качество ответа важнее скорости.

Практический совет: Используйте паттерн «Waterfall» (Водопад). Сначала попробуйте решить задачу моделью Flash. Если она не справляется (например, оценка уверенности низкая), делайте фолбэк на Pro. Это сохранит низкую среднюю задержку для 80% запросов.

Стратегия 2: Context Caching (Кэширование контекста)

Это «киллер-фича» Gemini для снижения задержки на этапе Prefill. Представьте, что вы загружаете в модель книгу на 500 страниц и задаете по ней вопросы. Без кэширования модель каждый раз заново «читает» эти 500 страниц (токенизирует и обрабатывает слои внимания).

Как это работает: Вы один раз отправляете большой контекст (документы, системные инструкции, примеры few-shot). Gemini сохраняет промежуточное состояние (KV-кэш) на своих серверах. При следующих запросах вы ссылаетесь на этот кэш. Фаза Prefill сокращается практически до нуля.

Когда применять:
1. Долгоживущие чат-боты с огромными системными промптами.
2. RAG-системы, где контекст документов не меняется часто.
3. Анализ длинных видео или аудио.

python
import google.genai as genai
import datetime

# Пример создания кэша для большого документа

# 1. Загружаем большой файл (условно)
# big_file = client.files.upload(path='handbook.pdf')

# 2. Создаем кэш
cache = client.caches.create(
    model='models/gemini-1.5-flash-001', # Убедитесь в поддержке версии
    config={
        'display_name': 'Company Handbook Cache',
        'ttl': '3600s', # Время жизни кэша (1 час)
        'contents': [
            # Ссылка на загруженный файл или большой текст
            # {'role': 'user', 'parts': [{'file_data': {'file_uri': big_file.uri}}]}
             {'role': 'user', 'parts': [{'text': '...ОЧЕНЬ ДЛИННЫЙ ТЕКСТ ИНСТРУКЦИЙ...'}]}
        ]
    }
)

print(f"Кэш создан: {cache.name}")

# 3. Использование кэша в запросе
# Обратите внимание: Prefill time будет минимальным
start = time.time()
response = client.models.generate_content(
    model='models/gemini-1.5-flash-001',
    contents="Какая политика по отпускам?",
    config={'cached_content': cache.name} # Ссылаемся на кэш
)
print(f"Ответ получен за {time.time() - start:.4f} сек: {response.text[:50]}...")

Стратегия 3: Оптимизация Промпта (Prompt Engineering)

Длина промпта напрямую влияет на TTFT (фаза Prefill), а длина желаемого ответа — на общее время (фаза Decoding). Тут работает простая математика.

  • Убирайте «воду»: Инструкция «Будь вежлив и профессионален, отвечай, используя деловой стиль» занимает токены. Если модель по умолчанию вежлива, уберите это.
  • Ограничивайте вывод (`max_output_tokens`): Если вам нужно просто «Да» или «Нет», жестко ограничьте генерацию 5 токенами. Это предотвратит случаи, когда модель начинает «рассуждать» после ответа, тратя время и деньги.
  • Синтаксис имеет значение: Для структурированных данных (JSON) используйте режим `response_mime_type="application/json"` или `response_schema`. Модели Gemini 3 оптимизированы для генерации JSON и делают это быстрее, чем при попытке сформировать JSON через текстовые уговоры.

Стратегия 4: Semantic Caching (Ваш уровень)

Самый быстрый запрос — тот, который не дошел до LLM. Если пользователь спрашивает «Как сбросить пароль?», и вы уже отвечали на это 100 раз, нет смысла дергать Gemini.

Используйте векторную базу данных (например, Chroma, Pinecone) для хранения пар «Вопрос — Ответ».
1. Пришел запрос.
2. Векторизуем его.
3. Ищем похожий вопрос в базе (similarity > 0.9).
4. Если нашли — отдаем сохраненный ответ мгновенно (50-100 мс).
5. Если нет — идем к Gemini.

Упражнение

Создайте скрипт 'Latency Benchmark', который сравнивает два сценария для одной и той же задачи (например, саммаризация текста): 1. Обычный запрос (без стриминга). 2. Стриминговый запрос (с измерением TTFT). Выведите разницу во времени до появления первого полезного контента.

Вопрос

Вы разрабатываете чат-бота для техподдержки, который использует огромную базу знаний (500 страниц документации) в системном промпте. Какой метод будет НАИБОЛЕЕ эффективен для снижения задержки первого токена (TTFT) при повторных запросах?