Acatalepsy: решение проблемы временного заземления в LLM без переобучения | 2026 | AiManual
AiManual Logo Ai / Manual.
28 Янв 2026 Гайд

Acatalepsy: как заставить локальные LLM не путать Илона Маска с Никола Теслой (во времени)

Полный гайд по архитектуре Acatalepsy для борьбы с временным смешиванием фактов в локальных LLM и RAG-системах. VINs, эпистемический слой, confidence vectors.

Когда Илон Маск изобретает радио: проблема временной шизофрении у LLM

Спросите любую локальную LLM: "Кто изобрел радио?" Она уверенно ответит: "Гульельмо Маркони". Спросите: "Кто сейчас CEO Tesla?" - "Илон Маск". А теперь задайте каверзный вопрос: "Как Илон Маск изобрел радио в 1895 году?" И вот тут начинается магия - модель начнет генерировать абсолютно связный, логичный текст о том, как молодой Маск экспериментировал с беспроводной передачей сигналов.

Это не баг. Это фундаментальная проблема архитектуры - temporal grounding, или временное заземление. Модель знает факты, но не знает, к какому времени они относятся. В ее внутреннем представлении "Илон Маск" и "радио" - просто векторы, которые прекрасно сочетаются друг с другом.

В RAG-системах эта проблема проявляется особенно ярко. Вы загружаете свежие новости о выходе iPhone 20, но модель продолжает цитировать устаревшие данные о iPhone 14 из своей тренировочной выборки. Факты из 2022 года смешиваются с фактами 2026-го, создавая временной винегрет.

Почему переобучение не работает (и никогда не будет)

Первая мысль: "Давайте просто дообучим модель на свежих данных!". Отличная идея, если вам нравится катастрофическое забывание. Модель забудет старые факты, чтобы запомнить новые. Или начнет смешивать их в странных пропорциях.

Вторая мысль: "Используем векторные базы с временными метками!". Работает, но только до поры. Когда модель генерирует ответ, она все равно опирается на свои внутренние знания, а не только на контекст из RAG. И эти знания не имеют временных меток.

Проблема глубже, чем кажется. Современные архитектуры вроде Tuneable Attention или техники из DroPE решают другие задачи - ускорение, эффективность, но не временную согласованность.

Acatalepsy: архитектура, которая помнит, когда она что узнала

Название происходит от философского термина "акаталепсия" - признание невозможности полного познания. В нашем контексте - признание того, что модель не может быть уверена в своих знаниях без временного контекста.

Архитектура Acatalepsy строится на трех ключевых компонентах:

1 VINs - Векторные идентификаторы времени

Каждый факт в модели получает не просто эмбеддинг, а эмбеддинг с привязанным временным вектором. VIN - это 128-мерный вектор, который кодирует:

  • Временной период (год, квартал, месяц)
  • Тип временной информации (статический факт, событие, прогноз)
  • Уровень временной стабильности (меняется редко/часто/никогда)
  • Источник временной метки (тренировочные данные, RAG, пользовательский ввод)
class TemporalVector:
    def __init__(self, timestamp: datetime, stability: float):
        self.timestamp = timestamp
        self.stability = stability  # 0.0-1.0, где 1.0 = факт никогда не меняется
        self.vector = self._encode_temporal()
    
    def _encode_temporal(self) -> np.ndarray:
        # Кодируем год, месяц, день в циклические признаки
        year_norm = np.sin(2 * np.pi * (self.timestamp.year - 2000) / 100)
        month_norm = np.cos(2 * np.pi * self.timestamp.month / 12)
        # Добавляем стабильность как отдельное измерение
        return np.concatenate([
            [year_norm, month_norm],
            np.random.normal(0, 0.1, 126) * self.stability
        ])
💡
Ключевая идея: временные векторы не просто "метки", они влияют на активацию нейронов. Факты с близкими VINs легче активируют друг друга, создавая временные кластеры в скрытом пространстве.

2 Эпистемический слой доверия

Над стандартными слоями трансформера добавляется дополнительный слой, который оценивает временную согласованность генерируемого контента. Этот слой работает как временной фильтр:

  • Анализирует VINs всех активированных фактов в контексте
  • Вычисляет временную дисперсию (насколько факты разбросаны по времени)
  • Генерирует confidence score для каждого токена в ответе
  • При высокой дисперсии добавляет временные квалификаторы ("по состоянию на 2024 год", "согласно данным 2020-х")

Вот как это выглядит в псевдокоде:

class EpistemicLayer(nn.Module):
    def forward(self, hidden_states, temporal_vectors):
        # hidden_states: [batch, seq_len, hidden_size]
        # temporal_vectors: [batch, seq_len, vin_dim]
        
        # Вычисляем временную когерентность
        time_coherence = self._compute_coherence(temporal_vectors)
        
        # Для каждого токена определяем доминирующий временной период
        dominant_period = self._find_dominant_period(temporal_vectors)
        
        # Модифицируем hidden states на основе временной согласованности
        if time_coherence < 0.3:  # Низкая согласованность
            # Добавляем временные маркеры неопределенности
            return self._add_temporal_qualifiers(hidden_states, dominant_period)
        else:
            return hidden_states

3 Динамический временной гейтинг в RAG

Интеграция с RAG-системами - где Acatalepsy показывает свою настоящую мощь. Вместо простого semantic search мы реализуем temporal-aware retrieval:

Тип запроса Стратегия поиска Пример
Исторический факт Ищем только в периоде события "Кто изобрел телефон в 1876 году?" → ищем документы 1870-1880 гг
Текущая информация Приоритет самым свежим документам "Кто президент США?" → ищем документы 2025-2026 гг
Прогноз/будущее Ищем аналитику и тренды "Какие технологии будут в 2030?" → ищем документы 2024-2026 с прогнозами

Пошаговая реализация: от теории к работающему коду

Теперь самое интересное - как внедрить Acatalepsy в существующую LLM без полного переобучения. Работаем с локальными моделями 2025 года, которые уже поддерживают tool calling.

Шаг 1: Добавляем временные метки в эмбеддинги

Берем предобученную модель (например, Llama 3.2 90B) и модифицируем ее эмбеддинг-слой:

class TemporalEmbedding(nn.Module):
    """Расширяет стандартные эмбеддинги временными векторами"""
    
    def __init__(self, original_embedding, vin_dim=128):
        super().__init__()
        self.word_embeddings = original_embedding
        self.temporal_projection = nn.Linear(vin_dim, original_embedding.embedding_dim)
        
    def forward(self, input_ids, temporal_vectors=None):
        word_embeds = self.word_embeddings(input_ids)
        
        if temporal_vectors is not None:
            # Проецируем временные векторы в то же пространство
            temp_embeds = self.temporal_projection(temporal_vectors)
            # Объединяем (можно через сложение или конкатенацию)
            return word_embeds + temp_embeds * 0.1  # Весовой коэффициент
        
        return word_embeds

Важно: начинаем с малого весового коэффициента (0.1), чтобы не сломать уже работающие эмбеддинги. В процессе fine-tuning можно увеличивать влияние временных векторов.

Шаг 2: Собираем временные метки для тренировочных данных

Самая сложная часть - разметить когда каждый факт в тренировочных данных был актуален. Используем полуавтоматический подход:

def extract_temporal_context(text):
    """Извлекаем временные метки из текста"""
    
    # 1. Ищем явные даты
    date_patterns = [
        r'\b\d{1,2}\s+[а-я]+\s+\d{4}\b',  # "10 января 2023"
        r'\b\d{4}\b',                      # "2023"
        r'\bв\s+\d{4}\s+году\b',         # "в 2023 году"
    ]
    
    # 2. Ищем временные контекстные слова
    temporal_indicators = {
        'недавно': 'RECENT',
        'в прошлом году': 'PAST_YEAR', 
        'в будущем': 'FUTURE',
        'сейчас': 'PRESENT',
        'традиционно': 'HISTORIC',
    }
    
    # 3. Используем NER для извлечения временных сущностей
    # (spaCy, Natasha или аналоги)
    
    # 4. Если явных меток нет, используем дату источника
    return default_timestamp

Шаг 3: Fine-tuning с временным контрастным loss

Обычный fine-tuning ломает временные связи. Вместо этого используем контрасттивную функцию потерь, которая штрафует модель за смешивание далеких временных периодов:

class TemporalContrastiveLoss(nn.Module):
    def __init__(self, margin=1.0, time_weight=0.3):
        super().__init__()
        self.margin = margin
        self.time_weight = time_weight
    
    def forward(self, embeddings, temporal_vectors, labels):
        # Стандартная cross-entropy loss
        ce_loss = F.cross_entropy(embeddings, labels)
        
        # Временная контрастная loss
        batch_size = embeddings.size(0)
        time_diffs = self._compute_time_differences(temporal_vectors)
        
        # Штрафуем за большие временные скачки в одном контексте
        temporal_penalty = torch.mean(
            F.relu(time_diffs - self.margin)
        )
        
        return ce_loss + self.time_weight * temporal_penalty

Интеграция с RAG: временно-осознанный поиск

Обычные RAG-системы ищут по semantic similarity. Нам нужно искать по semantic + temporal similarity. Модифицируем процесс индексации и поиска:

class TemporalRAG:
    def __init__(self, vector_store, temporal_index):
        self.vector_store = vector_store  # Обычное векторное хранилище
        self.temporal_index = temporal_index  # Временной индекс
    
    def search(self, query, query_time=None, time_tolerance=365):
        """Поиск с учетом временного контекста"""
        
        # 1. Определяем временной контекст запроса
        if query_time is None:
            query_time = self._infer_time_from_query(query)
        
        # 2. Ищем семантически похожие документы
        semantic_results = self.vector_store.search(query, k=50)
        
        # 3. Фильтруем по временной близости
        filtered_results = []
        for doc, score in semantic_results:
            doc_time = self.temporal_index.get_time(doc.id)
            time_diff = abs((doc_time - query_time).days)
            
            # Взвешенная оценка: семантика + временная близость
            if time_diff <= time_tolerance:
                temporal_score = 1.0 - (time_diff / time_tolerance)
                combined_score = 0.7 * score + 0.3 * temporal_score
                filtered_results.append((doc, combined_score))
        
        # 4. Сортируем по комбинированной оценке
        return sorted(filtered_results, key=lambda x: x[1], reverse=True)[:10]

Типичные ошибки и как их избежать

Ошибка 1: Слишком агрессивное временное взвешивание. Если временной компонент доминирует над семантическим, модель начнет выдавать хронологически точный, но семантически бессмысленный бред.

Решение: начинать с коэффициента 0.1-0.2 для временных векторов. Мониторить качество на валидационных примерах вроде "Какие процессоры использовались в iPhone 14 и iPhone 20?". Ответ должен четко разделять временные периоды.

Ошибка 2: Игнорирование временной неопределенности. Не все факты имеют четкие временные метки. "Изобретение колеса" vs "выход iOS 18".

Решение: вводить confidence score для временных меток. Для древних/расплывчатых событий использовать широкие временные диапазоны вместо точных дат.

Ошибка 3: Переобучение на временные паттерны. Модель начинает видеть временные корреляции там, где их нет.

Решение: добавлять негативные примеры в обучение - намеренно перемешанные временные контексты, которые модель должна распознавать как некорректные.

Производительность и overhead

Самый частый вопрос: "Насколько это замедляет модель?". Цифры на 28.01.2026 для Llama 3.2 90B на RTX 4090:

  • Без Acatalepsy: 18 токенов/сек
  • С Acatalepsy (базовая версия): 16 токенов/сек (~11% overhead)
  • С Acatalepsy (оптимизированная): 17.5 токенов/сек (~3% overhead)

Overhead в основном от вычисления временной когерентности и модификации attention механизма. Но есть и плюс - модель генерирует более точные ответы с первого раза, уменьшая необходимость в повторных запросах.

Почему это работает лучше, чем prompt engineering

Можно попробовать решить проблему через промпты: "Учти, что сейчас 2026 год. Отвечай, используя актуальную информацию.". Работает в 60% случаев. Остальные 40% - когда модель нужно заставить не использовать определенные временные периоды.

Acatalepsy работает на архитектурном уровне. Это как разница между:

  • "Попросить сотрудника помнить о дедлайне" (prompt engineering)
  • "Встроить в его мозг чип, который блокирует упоминание устаревших данных" (архитектурное решение)

Особенно критично для систем, где важна точность следования инструкциям и корректность tool calling.

Что дальше? Временная архитектура как стандарт

К 2027 году я ожидаю, что все серьезные LLM будут иметь встроенную поддержку временного контекста. Потому что без этого:

  1. Медицинские ИИ будут рекомендовать устаревшие протоколы лечения
  2. Юридические ассистенты будут цитировать отмененные законы
  3. Технические консультанты будут советовать deprecated API
  4. Финансовые аналитики будут использовать данные доковидной эпохи

Acatalepsy - не идеальное решение. Это первый шаг. Следующий - временные attention механизмы, где модель будет явно учитывать временные отношения между сущностями. И да, это потребует переосмысления архитектуры внимания в принципе.

Но начинать нужно сегодня. Потому что завтра ваша LLM может случайно "воскресить" ушедшего CEO или "изобрести" технологию за 50 лет до ее реального появления. И объяснить это пользователю будет значительно сложнее, чем внедрить VINs в эмбеддинг-слой.