
Всем привет! Бездумно соглашаться с любыми хотелками заказчика или начальства в технических вопросах — почти то же самое, что саботировать проект: всё это быстро превращается в тяжёлый технический долг. Да, жёсткие сроки, ограниченный бюджет и нехватка "свободных рук" — реальность, с которой приходится считаться. Но это не отменяет простой вещи: свои опасения и архитектурные риски нужно озвучивать, выносить на обсуждение и предлагать не только «работающие на сейчас», но и масштабируемые решения.
Как разработчикам нам обычно говорят: «давайте максимально быстро и топорно соберём proof-of-concept (PoC)». Мы собираем PoC на костылях, а дальше слышим: «отлично, теперь давайте из этого сделаем MVP». Времени на переорганизацию и реинжиниринг архитектуры никто не даёт. В итоге недели и месяцы работы превращают проект в тупиковую поделку — груду классов, методов и промптов, к которой страшно прикасаться.
С LLM эта история становится ещё болезненнее. В работе у меня было несколько показательных проектов с LLM в роли основного движка (RAG, Q&A-системы), на которых я очень наглядно увидел, как делать не стоит. Эти «шишки» превратились в набор антипаттернов проектирования LLM-приложений, о которых я хочу поговорить в серии статей.
В этой части — антипаттерн взаимодействия с LLM, когда модель игнорирует контекст: важные детали промпта, куски документов и даже прямые инструкции.
Представьте ситуацию: вы даёте модели текст, в котором прямо содержится ответ на вопрос, но она отвечает что-то совсем не то. Вы прописываете инструкции, как именно нужно вести диалог и решать задачу, но они стабильно игнорируются. Вы добавляете новые чанки с данными, дописываете всё более подробные правила и уточнения — а качество ответов только падает.
Системный промпт разрастается до нереальных размеров: на вход модель получает условные десять страниц А4 мелким шрифтом, а адекватные ответы появляются в двух случаях из десяти — и то на тех примерах, на которых вы изначально тренировались во время разработки.
Что с этим делать? На первый взгляд — выкинуть большую часть входящей информации. Мы все знаем формулу: «мусор на входе = мусор на выходе». Проблема в том, что всё, что вы подаёте в LLM, кажется важным и нужным — и я на 99% с этим согласен. Но есть одно «но»: модель не умеет одинаково эффективно работать со всей входящей информацией. Часть контекста она использует, часть — теряет, а где-то просто «залипает» на не самых важных деталях.
И один из самых неприятных эффектов здесь — так называемый «lost in the middle» («потерянный в середине»).
Потерянный в середине. Как и почему модели игнорируют информацию в середине контекста?
Долгое время исследования больших языковых моделей были сосредоточены на том, чтобы вообще научить их понимать естественный язык и связно продолжать текст. Когда это стало получаться, модели начали встраивать в реальные продукты — и логичным следующим шагом стал вопрос: как научить их эффективно работать с длинным контекстом?
На практике довольно быстро обнаружился неприятный эффект: модели неплохо справляются с небольшими фрагментами текста, но по мере роста объёма входных данных качество ответов заметно проседает. Особенно плохо обрабатывается информация, спрятанная где-то в середине длинного промпта.
Этот эффект получил название «lost in the middle». В одноимённой работе Lost in the Middle: How Language Models Use Long Contexts [1] авторы показывают, что по мере роста длины контекста модели всё хуже извлекают факты из середины — даже когда ответ буквально есть в подсунутом им тексте.
Как показали, что модели теряют середину контекста?
В работе исследователи смоделировали типичный сценарий RAG-приложений.
Они взяли реальный вопрос, один абзац текста, в котором точно есть ответ, и несколько абзацев-«шумов», где ответа нет. Дальше делали две вещи:
двигали «правильный» абзац по контексту — в начало, середину, конец;
меняли длину промпта, добавляя всё больше лишних абзацев вокруг.
При этом задача не менялась: тот же вопрос, тот же абзац с ответом, менялась только его позиция и объём «мусора» вокруг.

На графиках (рис. 1) всё выглядит очень просто:
модели лучше всего отвечают, когда нужный фрагмент стоит в самом начале или в самом конце контекста.
Как только тот же самый абзац сдвигают в середину, качество заметно падает. Чем длиннее промпт, тем глубже провал. В некоторых случаях ответ получается даже хуже, чем если вообще не давать модели никаких документов.
Как проверили, что дело не в «сложном тексте»?
Логичный вопрос: может, модели просто тяжело даётся текст Википедии, похожие абзацы, нюансы формулировок — и дело не в позиции, а в семантике?
Чтобы это исключить, авторы делают два шага.
Во-первых, они аккуратно чистят постановку эксперимента:
убирают вопросы, где можно придумать несколько разумных ответов;
пробуют подмешивать вообще случайные абзацы вместо «похожих»;
меняют порядок документов и прямо пишут в инструкции, что список перемешан.
Во всех этих вариантах характерная U-образная кривая по позиции никуда не девается: начало и конец работают лучше, середина — хуже.
Во-вторых, они полностью выкидывают естественный язык и ставят синтетический эксперимент.
Контекстом становится длинный список пар «ключ–значение», записанный в виде JSON. И ключ, и значение — случайные идентификаторы. Задача — по заданному ��лючу найти нужное значение. Никакой семантики, только точное совпадение токенов.
И здесь история повторяется: часть моделей уверенно находит пару, если она в начале или в конце списка, и заметно чаще ошибается, если нужный ключ лежит где-то в середине. То есть эффект «потерянного в середине» проявляется даже там, где нет текста как такового, есть только поиск нужного кусочка строки в длинном списке.
Какие причины считали наиболее вероятными
Авторы не предлагают одну причину, но выделяют несколько факторов, которые, скорее всего, складываются вместе.
1. Архитектура и длина, на которой модель реально училась.
Модели энкодер-декодер оказались устойчивее к положению факта — пока длина контекста остаётся в пределах привычного для них окна. Как только промпт становится длиннее, чем при обучении (претрейне), у неё появляется тот же провал в середине, что и у остальных.
2. Формат данных и привычки модели.
Во многих обучающих и дообучающих датасетах важные вещи лежат либо в начале (описание задачи, системный промпт), либо в конце (ответ, вывод). Модель привыкает доверять этим областям сильнее. Когда мы даём ей очень длинный промпт с кучей документов, это поведение сохраняется: начало и конец кажутся «важнее», середина — менее значимой.
3. Масштаб модели.
На небольших моделях чаще всего видно только «эффект конца»: модель в первую очередь опирается на последние токены. У более крупных моделей появляется уже полноценное «начало + конец сильнее», и провал в середине становится заметнее.
Главный практический вывод из этой работы можно сформулировать так:
Сам по себе заявленный размер окна — «32k», «100k» и т.п. — мало что говорит. Важно не только сколько токенов модель может принять, но и как сильно падает качество, когда нужный факт лежит не в начале и не в конце, а в середине этого окна.
Именно поэтому простое «давайте добавим ещё больше контекста, хуже не будет» — на практике превращается в один из антипаттернов при проектировании LLM-систем.
Найденный в середине. Как показали, что модель видит середину, но не может ее эффективно использовать.
Про сам эффект «lost in the middle» сегодня знают многие разработчики и исследователи. Но на этом история не закончилась: следом появились работы, которые пытаются ответить на более тонкий вопрос:
модель реально «теряет» середину контекста — или её внимание до этой середины просто не добирается?
В статье Found in the Middle: Calibrating Positional Attention Bias Improves Long Context Utilization [2] авторы повторяют схему оригинального эксперимента и приходят к довольно интересному выводу:
проблема «lost in the middle» может быть не фундаментальной неспособностью модели, а следствием сильного позиционного смещения во внимании (positional attention bias).
По сути, это означает следующее. Модель на самом деле может правильно оценить релевантность документа в середине, но её внутреннее внимание систематически завышает ценность начала и конца и занижает середину. Из-за этого «правильный» сигнал теряется на фоне позиционного шума. Если это смещение убрать, картина внимания становится гораздо ближе к тому, как выглядела бы реальная релевантность документов.
Как они это проверяют?
Берут один и тот же документ и по очереди вставляют его в разные места промпта: в начало, в середину, в конец (аналогично эксперименту Lost in the Middle [1]).
Смотрят на сырые attention score’ы модели к этому документу в каждой позиции.
Видят знакомую U-образную кривую: высокое внимание в начале, высокое в конце и провал в середине — при том, что содержимое документа не меняется.
Дальше авторы трактуют внимание как сумму двух слагаемых:
«внимание к документу» = «его реальная полезность» + «позиционное искажение».
И пытаются отделить одно от другого. Для этого они оценивают позиционное смещение как функцию от позиции и вычитают его из исходных attention score’ов. Получается калиброванное внимание — уже без влияния позиции, отражающее только семантику документа.
Оказалось, что после такой калибровки:
внимание моделей гораздо лучше коррелирует с фактической релевантностью документов;
если использовать калиброванное внимание как ранжировщик (то есть выбирать документы по нему), оно даёт заметный выигрыш по сравнению с "ванильным" вниманием и с популярными схемами retriever → reranker.
Для модели Vicuna-7B-v1.5-16k авторы на датасете NaturalQuestions показывают рост метрики Recall@3 (в задаче ранжирования контекстов для open-domain QA) до примерно +48 процентных пунктов относительно простого vanilla attention и ощутимый выигрыш относительно более продвинутых схем ранжирования.
То есть, грубо говоря, модель всё это время «знала», какие документы ей нужны, но позиционный bias мешал ей их нормально использовать.
Причины позиционного смещения.
В работе Found in the Middle [2] показали, что существует позиционное смещение, но не делали глубокого анализа причин его появления. И на фоне этого появляется достаточно большое количество исследований, которые начинают изучать причины смещения. Эти исследования хорошо обозрены и суммированы в список наиболее вероятных причин в работе Found in the Middle: How Language Models Use Long Contexts Better via Plug-and-Play Positional Encoding [3]:
Каузальное внимание усиливает вклад начальных токенов уже на этапе обучения.
В декодерных трансформерах используется каузальное внимание: каждый токен на каждом слое может смотреть только на предыдущие токены. Из-за этого токены в начале последовательности участвуют в большем числе операций внимания и переходов по слоям, чем токены ближе к концу.
На этапе предобучения модель многократно учится предсказывать следующие токены по одному и тому же префиксу, поэтому именно первые позиции оказываются статистически наиболее важными для минимизации функции потерь. В результате формируется устойчивое позиционное смещение: при прочих равных модель систематически присваивает более высокие веса вниманию к началу контекста.
Ротационное позиционное кодирование ослабляет влияние далёких токенов.
Ротационное позиционное кодирование (RoPE), которое сейчас используют почти во всех крупных моделях, хорошо работает на коротких расстояниях, но на очень длинных контекстах приводит к эффекту «затухания на расстоянии». Чем дальше токен по позиции, тем слабее его вклад в внимание, даже если он семантически важен. В сочетании с каузальным вниманием это приводит к тому, что середина длинного контекста оказывается в «слепой зоне»: она уже далеко от начала и ещё не попадает в область свежего «хвоста».
Что такое ротационное позиционное кодирование (RoPE) и почему происходит затухание внимания для дальних токенов
Зачем вообще нужно позиционное кодирование?
Базовый трансформер оперирует наборами векторных представлений токенов и по определению инвариантен к перестановке элементов: если просто подать в него эмбеддинги без явной информации о позициях, модель не сможет различить, в каком порядке шли токены. Чтобы восстановить чувствительность к порядку, к эмбеддингу каждого токена добавляется (или встраивается через другую операцию) позиционное кодирование — вектор, однозначно зависящий от индекса позиции. Таким образом, self-attention получает на вход не только информацию о значении токена, но и о его месте в последовательности.
Что такое RoPE?
Базой для большинства современных «длинноконтекстных» LLM служит ротационное позиционное кодирование RoPE, предложенное в работе RoFormer: Enhanced Transformer with Rotary Position Embedding [4]. В RoPE положение токена задаётся как последовательность вращений в комплексном (или 2D) пространстве, и скалярное произведение между двумя токенами после такого кодирования зависит только от их относительного смещения. За счёт этого RoPE естественным образом реализует убывающую зависимость между токенами: чем больше расстояние по позиции, тем ниже ожидаемая корреляция, и тем меньше вклад далёкого токена в attention. Авторы RoFormer рассматривают это как полезное свойство — модель «по умолчанию» сильнее опирается на локальный контекст, а влияние очень удалённых токенов затухает с ростом расстояния.
Почему появляется long-term decay (долгосрочное затухание)?
Проблему long-term decay (долгосрочное затухание) RoPE уже отдельно подсвечивают в ряде работ, которые изучают поведение позиционных кодировок в длинном контексте. В частности, в статье Layer-Specific Scaling of Positional Encodings for Superior Long-Context Modeling [5] показано, что по мере роста относительного расстояния между токенами - attention score между текущим токеном и дальними токенами быстро затухает. В авторегрессионном режиме это приводит к тому, что модель всё больше опирается на ближайшие токены «хвоста» последовательности, а информация из далёких токенов — включая середину контекста — начинает оказывать практически пренебрежимо малое влияние на генерацию следующего токена.
Распределение эффекта по слоям и головам внимания.
Анализ распределения attention-весов по слоям и головам показывает, что long-term decay — не артефакт одной отдельной головы, а характерный паттерн для значительной части attention-голов. В работах п�� Ms-PoE [3] и Mixture of In-Context Experts Enhance LLMs' Long Context Awareness [6] видно, что разные головы обладают различной позиционной чувствительностью: для части голов RoPE порождает выраженные «волновые» профили внимания с провалами по целым диапазонам позиций, где веса внимания близки к нулю. Именно поэтому авторы этих методов вводят «head-wise» модификации позиционного кодирования — назначают разным головам разные масштабирующие коэффициенты либо используют несколько наборов RoPE-параметров на голову — чтобы ослабить «long-term decay» и вернуть удалённым токенам (в том числе из середины контекста) заметный вклад в генерацию.
Что дальше?
На этом история с «lost in the middle» не заканчивается. Мы увидели, что:
модели действительно теряют середину контекста, и это проявляется даже в синтетических задачах без семантики;
на уровне внимания это выглядит как сильное позиционное смещение: начало и конец получают завышенный вес, середина — заниженный;
часть этого смещения объясняется архитектурой (каузальное внимание) и способом позиционного кодирования (RoPE и long-term decay).
В следующей части поговорим о том, как это выглядит на практике:
какие бенчмарки сейчас используют, чтобы измерять реальное «рабочее» окно контекста у моделей с учетом эффекта «lost in the middle» и общей деградации качества работы при увеличении контекста;
почему эффективное окно контекста оказывается в разы меньше заявленного окна и каков его размер на практике;
какие архитектурные решения помогают не убиться об эти ограничения при проектировании LLM-приложений.
Если тема кажется вам интересной, я продолжаю разбирать подобные вещи у себя в Telegram короткими постами, экспериментами и примерами из практики: «надо разобраться | заставляем LLM работать».
А также об общих принципах генерации кода с LLM я писал в статье «Сама не разберётся: мои 7 принципов генерации кода с LLM». Понимание эффекта «lost in the middle» и его фундаментальных причин хорошо ложится в некоторые принципы, в том числе — не давать всё и сразу, а описывать окружающий код и сигнатуры вместо самого кода и т.п.
Литература
Found in the Middle: Calibrating Positional Attention Bias Improves Long Context Utilization
RoFormer: Enhanced Transformer with Rotary Position Embedding
Layer-Specific Scaling of Positional Encodings for Superior Long-Context Modeling
6. Mixture of In-Context Experts Enhance LLMs' Long Context Awareness