Эпидемиология дампов памяти: исправление ошибки 18-летней давности | OpenAI
Использование анализа на уровне популяции для отладки сложных сбоев в нашей инфраструктуре данных.
- Первая попытка отладки: тщательное изучение нескольких дампов памяти.
- Подсказки из стека
- Врач или эпидемиолог?
- Очистка данных
- Ошибка №1: некорректный хост
- Обработка исключений представляет собой динамическую передачу управления.
- Отменяем последнее предположение
- Ошибка №2: ошибка в libunwind
- Почему основные показатели доходности маскируются под обычные плохие результаты?
- Окно гонки одной инструкции
- Почему ошибка в libunwind появилась именно сейчас?
- Эффективность диагностики на уровне популяции
Оглавление
- Первая попытка отладки: тщательное изучение нескольких дампов памяти.
- Подсказки из стека
- Врач или эпидемиолог?
- Очистка данных
- Ошибка №1: некорректный хост
- Обработка исключений представляет собой динамическую передачу управления.
- Отменяем последнее предположение
- Ошибка №2: ошибка в libunwind
- Почему основные показатели доходности маскируются под обычные плохие результаты?
- Окно гонки одной инструкции
- Почему ошибка в libunwind появилась именно сейчас?
- Эффективность диагностики на уровне популяции
Модели и агенты OpenAI все чаще полагаются на масштабируемую инфраструктуру данных для поиска релевантных данных во время вывода: когда модели обдумывают ваш вопрос. Некоторые из этих сервисов написаны на C++, низкоуровневое управление системой которого позволяет нам максимизировать производительность и минимизировать использование памяти. Эти преимущества в эффективности важны по мере масштабирования, но отсутствие безопасности памяти в C++ означает, что ошибки могут привести к сбоям из-за записи по некорректным или несуществующим адресам памяти.
Несколько месяцев назад мы наблюдали сбои в работе сервиса Rockset, специализированной части нашей инфраструктуры данных ChatGPT, которая является ключевой для многих плагинов данных и для поиска по диалогам. В каждом из этих сбоев обычная функция C++ завершалась, а затем возвращалась по некорректному адресу, что приводило к остановке программы ядром, поскольку указатель инструкций больше не указывал на код. Иногда адрес возврата в стековом кадре был равен NULL. Иногда регистр указателя стека в ЦП, казалось, смещался на 8 байт, как будто %rsp каким-то образом уменьшался в середине нормального выполнения. В обоих случаях сбой происходил при возврате.
Это не типичные ошибки для кода приложения. Случайная запись, попадающая только на сохраненный адрес возврата, возможна, но крайне маловероятна. Ошибка, которая смещает %rsp на 8 без участия встроенного ассемблера, setcontext или longjmp (ни один из которых мы не используем), еще более странная, поскольку скомпилированный код корректирует этот регистр непосредственно в прологе и эпилоге функции. Каждая гипотеза, которую мы (или ChatGPT) могли придумать, имела веские доказательства против себя, поэтому ошибка казалась невозможной.
То, что мы считали одной проблемой, в итоге оказалось двумя несвязанными ошибками, обнаруженными одновременно. Во-первых, незаметное повреждение оборудования на одном из хостов Azure, когда процессор просто некорректно выполнял вычисления. Во-вторых, 18-летняя проблема с состоянием гонки в библиотеке GNU libunwind — незамеченная ошибка в широко используемой библиотеке с открытым исходным кодом.
В этом посте рассказывается о том, как мы выявляли и устраняли, казалось бы, необъяснимые аварии, руководствуясь эпидемиологическим мышлением и создав высококачественный набор данных обо всех случаях аварий.
Первая попытка отладки: тщательное изучение нескольких дампов памяти.
Для начала давайте подробнее рассмотрим Rockset. Это облачная система обработки данных для поиска и анализа в реальном времени, которую мы используем во многих внутренних задачах OpenAI, например, для синхронизирующих коннекторов (Rockset был приобретен OpenAI в 2024 году). Потоковые обновления используются для поддержания актуального индекса базы знаний рабочего пространства, чтобы ChatGPT мог искать релевантную информацию при ответе на вопросы или выполнении действий.
Исполнительный слой Rockset написан на C++. Язык C++ обеспечивает низкоуровневый доступ к ЦП, что хорошо для производительности и эффективности, но это означает, что ошибки в приложении могут приводить к некорректному доступу к памяти и ошибкам сегментации. Чтобы помочь в их обнаружении, мы используем обработчик фатальных сигналов folly для записи трассировки стека при сбое, а соответствующие дампы памяти (снимок состояния программы в момент сбоя) загружаем в хранилище больших двоичных объектов Azure для последующего анализа. Все потоки обработки запросов Rockset реплицируются, что минимизирует влияние сбоя на клиент. Однако каждая ошибка сегментации соответствует ошибке, которую необходимо исправить для достижения наших целей в области надежности и качества.
Наш первоначальный подход заключался в том, чтобы рассматривать эти дампы памяти как обычную задачу отладки: внимательно изучить несколько дампов памяти, сформулировать гипотезы и поочередно их исключать.
Большинство сбоев происходило в методе DocumentTree::updateDocument . В этих случаях, по-видимому, метод updateDocument вызывал некую неизвестную функцию X, стек повреждался во время активности X, а затем X возвращался по адресу, который не являлся исполняемым кодом. В некоторых случаях только что извлеченный кадр X выглядел корректным, за исключением того, что сохраненный адрес возврата был NULL. В других случаях сам указатель стека выглядел некорректным, но следующий корректный кадр по-прежнему, похоже, был методом updateDocument .
Мы не знали, когда именно происходит повреждение стека, что создавало огромное пространство для поиска. Метод `updateDocument` — большой метод, который часто встраивается, поэтому количество кандидатов на роль X было ошеломляющим.
Была ли это ошибка в нашем коде на C++? Проблема компилятора или компоновки? Проблема в одной из наших библиотек времени выполнения? Ошибка ядра Linux, связанная с доставкой сигналов или переключением контекста? Что-то еще более редкое? Если это была случайная запись, почему она не была обнаружена нашей средой тестирования ASAN?
Мы попытались использовать журналы на уровне приложения, чтобы выявить все случаи возникновения проблемы, но ошибки, связанные с повреждением стека, трудно классифицировать только по журналам, поскольку сами трассировки стека повреждены или отсутствуют. Нам не удалось составить запрос к журналу, который не содержал бы как ложных срабатываний, так и ложных отрицаний. Мы вручную проверили больше ядер и обнаружили несколько дополнительных примеров, но этот процесс оказался слишком трудоемким, чтобы предоставить нам достоверный набор данных.
На этом этапе расследования мы (ошибочно) исключили аппаратную ошибку, поскольку наблюдали сбои в нескольких регионах и на разных типах оборудования, поэтому мы все еще искали причины, связанные исключительно с программным обеспечением. В течение нескольких дней мы очень тщательно изучали один сбой, связанный с неправильно выровненным регистром %rsp , восстанавливая историю до сбоя, используя содержимое стека и регистров. Это дало некоторые возможные подсказки, но поскольку мы не отказались от наших первоначальных выводов о том, что все ошибки имеют одну и ту же причину, это не помогло нам выйти из тупика.
Подсказки из стека
Прежде чем перейти к поворотному моменту нашего расследования, важно объяснить, какую информацию мы извлекали из основных файлов.
Rockset компилируется с параметром -fno-omit-frame-pointer , поэтому активный кадр стека всегда доступен через %rbp , а вызывающие функции формируют связанный список указателей кадров.
В Linux x86_64 протокол AMD64 System V ABI также резервирует 128 байт ниже %rsp в качестве «красной зоны». Эта область доступна для кода пользовательского пространства, и, что важно, ядро обязуется не перезаписывать её при передаче сигнала в соответствии с контрактом ABI.
Красная зона играла центральную роль в отладке сбоя после возврата из функции, поскольку она сохраняет некоторую информацию, предшествующую возврату. Когда срабатывает SIGSEGV , обработчик фатальных сигналов folly запускается в стеке потока, вызвавшего сбой. Кадры стека, которые больше не активны (поскольку их функция завершилась), будут перезаписаны обработчиком сигналов, за исключением последних 128 байт. Именно поэтому мы можем сказать что-то вроде: «Только что извлеченный из стека кадр X выглядел корректным, за исключением нулевого адреса возврата». Красная зона сохраняет некоторые неактивные кадры, а иногда только хвост одного неактивного кадра.

Мы обнаружили один сбой, связанный с несовпадением стека, в котором все задействованные функции были очень маленькими. Это позволило нам увидеть, что %rsp сместился во время выполнения относительно простой функции, и что последующие вызовы прошли успешно. Программа аварийно завершилась только тогда, когда активная функция наконец попыталась вернуться. Ни один из этих путей выполнения кода не использовал исключения, встроенный ассемблер, setcontext или longjmp , поэтому, если указатель стека действительно изменился так, как предполагало ядро, то никакая правдоподобная ошибка в коде пользовательского пространства не объясняла проблему.
Это подтолкнуло нас к изучению ядра.
Rockset использует сигналы более активно, чем большинство программ. Выполнение запросов разбито на множество легковесных задач, обменивающихся данными. Это важно для эффективной обработки больших объемов запросов в секунду, но затрудняет учет ресурсов ЦП для каждого запроса, поскольку работа над множеством запросов мультиплексируется на один и тот же пул потоков.
Наше решение называется coarse_thread_cputime_clock , оно достаточно дешево аппроксимирует clock_gettime(CLOCK_THREAD_CPUTIME_ID, …), чтобы производить выборку на границе каждой задачи. API timer_create можно использовать для планирования периодической доставки сигнала на основе нескольких представлений о течении времени, включая накопление процессорного времени. Мы планируем доставку сигнала (SIGUSR2) каждые несколько миллисекунд процессорного времени, после чего обработчик сигнала обновляет локальное значение потока. Хотя многие задачи не видят изменения грубого времени во время выполнения, суммирование всех дельт дает несмещенную оценку фактического процессорного времени для запроса.
Поскольку мы отправляем сигналы очень часто, редкая ошибка ядра, связанная с переключением контекста или доставкой сигналов, казалась вполне вероятной. Мы потратили время на изучение отчетов об ошибках, исходного кода ядра и патчей ядра, специфичных для Azure. Мы провели стресс-тесты. Нам не удалось найти ничего, что казалось бы связанным с проблемой.
В тот момент мы решили сделать шаг назад и попробовать другой подход.
Врач или эпидемиолог?
Существует два основных способа отладки подобных проблем.
Один из вариантов — действовать как своего рода врач: сосредоточиться на одном пациенте, провести множество анализов и попытаться диагностировать конкретный случай на основе подробных данных.
Другой подход заключается в том, чтобы действовать как эпидемиолог: изучить всю популяцию и задаться вопросом, есть ли закономерности, которые нельзя выявить на основе отдельного случая. Возникла ли ошибка в конкретной версии программного обеспечения? Коррелирует ли она с конкретной моделью оборудования (конкретным процессором и моделью сервера), одним регионом или одной версией ядра? Существуют ли несколько отдельных кластеров, скрывающихся внутри того, что выглядит как один синдром?
До этого мы в основном работали в режиме врача. Ключевым изменением стало решение о необходимости сбора высококачественных данных о населении.
Очистка данных
Наши предыдущие попытки автоматически найти все случаи возникновения проблемы потерпели неудачу, потому что мы пытались использовать текстовый поиск по логам. Сами дампы памяти содержат гораздо больше информации, но их ручной анализ оказался неэффективным. Мы решили вложить усилия в создание конвейера, который мог бы автоматически анализировать дампы памяти.
Мы попросили ChatGPT написать скрипт, который загружал префикс каждого файла дампа памяти, извлекал регистры, отфильтровывал известные ложные срабатывания с помощью логов и автоматически помечал сбой как возврат к нулю, несовпадение стека или другое. Затем мы запустили этот скрипт параллельно для всех дампов памяти Rockset, созданных в производственной среде за предыдущий год.
Это стало поворотным моментом.
Как только у нас появился чистый набор данных, корреляции проявились немедленно. То, что мы считали одной странной ошибкой, на самом деле оказалось двумя отдельными группами сбоев.
Ядра, возвращавшиеся к нулевому состоянию, были разбросаны по множеству кластеров и географических регионов. В последнее время их частота увеличилась, но четкой даты начала не было, как и ясных границ инфраструктуры.
Сбои, вызванные несовпадением стека, выглядели совершенно иначе. Все они происходили в одном регионе, имели четкую дату начала и никогда не случались на узлах, которые работали длительное время. Хотя в них участвовало несколько виртуальных машин Azure (виртуальных машин, размещенных в облаке), картина выглядела так, будто одна физическая машина с неисправным оборудованием вызывала проблемы для той виртуальной машины, которая случайно оказалась на ней.
В тот момент мы поняли, что мысленно смешивали две разные ошибки. Поскольку мы приводили контрпримеры из обеих ошибок, мы не могли найти единого связного объяснения.
Ошибка №1: некорректный хост
Имея под рукой чистый список узлов Kubernetes и временных меток, мы смогли отследить причины сбоев, связанных с несовпадением стека, до одного физического хоста, который было легко внести в черный список.
Нам не удалось воспроизвести повреждение регистров на этом хосте в контролируемой среде, даже после нескольких недель стресс-тестирования. Однако после вывода проблемного хоста из эксплуатации сбои, связанные с неправильным выравниванием стека, исчезли.
Удаление неисправного хоста не является постоянным решением в том смысле, что оно не предотвращает повторное возникновение той же проблемы. Однако мы можем изменить программное обеспечение таким образом, чтобы в случае повторного возникновения аналогичной проблемы её было легко обнаружить и устранить. Мы улучшили наш обработчик фатальных сигналов, включив в него регистрацию состояния, что позволяет обнаруживать повторные сбои только по логам (дамп ядра не требуется). Мы изменили плоскость управления таким образом, чтобы виртуальные машины обычно использовались повторно, а не перезапускались, что значительно упрощает обнаружение неисправных узлов на нашем уровне инфраструктурного стека. Мы также обновили наши руководства по устранению неполадок (и ментальные модели нашей команды), чтобы включить эту возможность.
После выделения сбоев, вызванных некорректной работой хоста, стало гораздо проще анализировать оставшиеся ядра, возвращающие значение в нулевое состояние. Ранее мы исключили возможность размотки исключений, поскольку считали, что у нас есть контрпримеры: сбои в участках кода, где исключения явно не использовались. Но все эти контрпримеры были связаны с кластером аппаратных сбоев.
После повторного анализа оставшихся ядер, учитывая это обстоятельство, мы обнаружили, что этот вывод был совершенно неверным: все сбои происходили во время размотки исключений.
Обработка исключений представляет собой динамическую передачу управления.
Когда в C++ возникает исключение, среда выполнения должна определить, какой блок catch должен его принять и какие деструкторы или обработчики очистки должны выполниться. Компилятор генерирует эти метаданные, но фактическое сопоставление происходит динамически во время выполнения.
Размотка стека исключений фактически выполняется не функцией, вызывающей throw , а вспомогательными функциями, вызываемыми результирующим скомпилированным кодом. Эти подпрограммы времени выполнения проверяют стек, извлекают метаданные о функциях, найденных в стеке, динамически ищут обработчики очистки и блоки catch, а затем передают управление одному из этих мест. Передача управления включает в себя размотку всех промежуточных кадров стека (включая кадры вспомогательных функций).
В операционном плане это гораздо ближе к longjmp или коммутатору оптоволокна, чем к обычному вызову и возврату. Необходимо восстановить регистры сохранения вызываемой стороны, а также регистры стекового кадра %rbp и %rsp .
Наш бинарный файл был скомпилирован с двумя библиотеками, содержащими реализации функций, выполняющих размотку исключений C++: libgcc и GNU libunwind. Динамический компоновщик выбрал определения из GNU libunwind. Это нас удивило; мы ожидали, что реализация libgcc победит из-за правил версионирования символов; однако проверка запущенных бинарных файлов показала, что это не так.
Отменяем последнее предположение
На этом этапе наша рабочая гипотеза изменилась, поскольку мы отказались от еще одного предположения, которое сделали, когда считали, что ошибка всего одна.
Возможно, мы наблюдали не обычный возврат функции в NULL. Возможно, мы наблюдали передачу по механизму размотки стека — по сути, восстановление регистра в стиле setcontext — когда указатель на инструкцию назначения стал NULL до передачи управления. Другими словами, некорректные данные из библиотеки размотки стека, а не некорректный адрес возврата в стеке.
Это значительно сузило круг проблем. Либо библиотека GNU libunwind вычисляла неправильное целевое состояние, либо вычисляла правильное состояние, но что-то его искажало, прежде чем оно могло быть применено.
Мы изучили исходный код GNU libunwind и обнаружили, что он синтезирует структуру ucontext_t в стеке, заполняет требуемое состояние регистра для кадра обработчика очистки, а затем передает указатель на эту структуру внутренней ассемблерной подпрограмме: _Ux86_64_setcontext .
К этому моменту у нас были все необходимые компоненты.
Синтезированный ucontext_t находится в одном из кадров стека, который разматывается функцией _Ux86_64_setcontext во время её выполнения. Читала ли функция _Ux86_64_setcontext структуру после изменения значения %rsp , когда структура перестала быть частью активного стека? Это сделало бы её уязвимой для перезаписывания сигналом, например, часто используемым SIGUSR2 .
Ошибка №2: ошибка в libunwind
Ответ был утвердительный.
Вот последние шесть инструкций _Ux86_64_setcontext в используемой нами версии GNU libunwind, которые в основном состоят из инструкций mov , загружающих данные из памяти в целевой регистр:
Простой текст
1 74: mov UC_MCONTEXT_GREGS_RSP(%rdi),%rsp 2 75: 3 76: /* поместить адрес возврата в стек */ 4 77: mov UC_MCONTEXT_GREGS_RIP(%rdi),%rcx 5 78: push %rcx 6 79: 7 80: mov UC_MCONTEXT_GREGS_RCX(%rdi),%rcx 8 81: mov UC_MCONTEXT_GREGS_RDI(%rdi),%rdi 9 82: retq
( %rdi указывает на выделенный в стеке ucontext_t , а макросы UC_MCONTEXT_* просто разворачиваются до фиксированного смещения, по которому хранится конкретный регистр.)
Первая инструкция знаменует начало окна гонки. Она обновляет указатель %rsp , указывая на новый нижний уровень активного стека. Как только это происходит, структура, на которую указывает %rdi , перестает быть частью активного стека (или красной зоны) и становится доступной для ядра.
Обычно это не вызывает проблем, но если сигнал поступает точно в нужный (или неверный?) момент, ядро сформирует кадр сигнала по адресу %rsp-128 . Это может перезаписать память, на которую указывает %rdi .
Если это произойдёт до того, как следующая инструкция прочитает UC_MCONTEXT_GREGS_RIP(%rdi) , то восстановленный указатель инструкции может быть повреждён. В наших случаях сбоев он становился NULL.
В этом и заключается ошибка.
Почему основные показатели доходности маскируются под обычные плохие результаты?
Эта сборка также объясняет одно из наблюдений, которое нас озадачило: почему функция X имела значение NULL в слоте адреса возврата предыдущего кадра стека.
Функция setcontext была написана таким образом, чтобы восстанавливать все регистры, включая %rdi , поэтому она не может использовать этот регистр для чтения UC_MCONTEXT_GREGS_RIP(%rdi) в последний момент передачи управления. Вместо этого она считывает значение раньше, сохраняет его в стеке, восстанавливает еще несколько регистров, а затем использует retq для чтения сохраненного значения и передачи управления.
То, что в ядрах выглядело как «функция, вернувшая NULL», на самом деле означало «процесс размотки стека синтезировал целевой адрес возврата в стеке, но этот целевой адрес был поврежден до завершения передачи». Мы предполагали, что повреждение слота адреса возврата происходит на месте, поскольку нам не было известно о каких-либо местах, где (повреждаемые) данные были бы записаны в слот адреса возврата преднамеренно.
Окно гонки одной инструкции
Абсурдность этой ошибки заключается в очень узком окне гонки. В подобной ситуации гонки внешнее событие (сигнал) должно произойти между двумя шагами, выполняемыми другим потоком. Чем ближе эти шаги друг к другу, тем меньше вероятность возникновения состояния гонки.
В данном случае уязвимое окно буквально занимает ширину одной инструкции! Сигнал должен быть доставлен после изменения %rsp , но до загрузки следующей инструкции %rip . На современном суперскалярном процессоре с внеочередным выполнением инструкций за один цикл может выполняться несколько таких простых инструкций, поэтому окно гонки составляет примерно сто пикосекунд.
Когда мы обнаружили эту проблему с задержкой выполнения инструкций, нашей первой реакцией было предположение, что она слишком редка, чтобы объяснить наблюдаемую частоту сбоев. Мы наблюдали более десятка сбоев с возвратом в нулевое состояние в день по всему парку устройств. Могла ли одна задержка выполнения инструкций во время очистки исключений действительно объяснить это?
Мы обратились к оценке Ферма. Если уязвимое окно имеет порядок 10−1010^{-10} 1 0 − 10 секунд, а SIGUSR2 поступает каждые 10−210^{-2} 1 0 − 2 секунды процессорного времени, затем каждый обработчик исключения или блок catch имеет приблизительно 10−810^{-8} 1 0 − 8 вероятность проигрыша в гонке.
Rockset использует исключения как часть своего внутреннего механизма обратного давления при приеме данных. Один перегруженный хост может сгенерировать количество исключений порядка 10410^{4}. 1 0 4 исключения в секунду. Это означает, что среднее время между отказами хоста при использовании обратного давления составляет 10410^{4}. 1 0 4 секунды, или одно столкновение каждые несколько часов. В масштабах всего флота этого более чем достаточно, чтобы объяснить наблюдаемую частоту столкновений.
Почему ошибка в libunwind появилась именно сейчас?
Ошибка в библиотеке GNU libunwind существует уже более 18 лет и присутствовала в первой версии x86_64 , которая поддерживала размотку исключений C++.
Так почему же оно появилось именно сейчас?
Частота сбоев примерно пропорциональна количеству выброшенных исключений и количеству доставленных сигналов. Она также зависит от того, сколько места в стеке занимает обработчик сигналов.
Rockset отличается от других систем по всем трем параметрам. Мы часто генерируем исключения в рамках стандартного управления перегрузкой; мы необычно часто обрабатываем SIGUSR2 из-за параметра coarse_thread_cputime_clock ; а в начале этого года мы увеличили использование стека обработчиком SIGUSR2 , добавив вызов timer_getoverrun , чтобы учитывать объединенные сигналы.
Последнее изменение, по-видимому, оказалось важным. Если обработчик использует достаточно мало стека, он может не достичь и не перезаписать устаревшую память ucontext_t . До этого изменения мы вообще не наблюдали подобных сбоев. После изменения частота сбоев оставалась низкой до тех пор, пока мы не увеличили нагрузку для некоторых сценариев использования, которые создавали дополнительную нагрузку на механизм обратного давления.
Другими словами, ошибка в libunwind существовала всегда, но результат, полученный на основе частоты исключений, частоты сигналов и использования стека обработчиков, лишь недавно превысил порог, при котором она стала заметной в процессе работы.
Этот механизм также объясняет совпадение, что как аппаратная ошибка, так и ошибка libunwind приводили к сбоям преимущественно внутри DocumentTree::updateDocument . Сбои, вызванные libunwind, были в значительной степени связаны с этим методом, поскольку он всегда активен в момент генерации исключения для применения обратного давления при приеме данных. Он также был в значительной степени выбран для сбоев, связанных с %rsp -misalignment, потому что неисправный аппаратный узел принадлежал к той же модели, которую мы используем для массового приема данных, и большая часть процессорного времени тратится именно на этот метод.
В качестве первоочередной меры мы перешли с GNU libunwind на механизм размотки стека из libgcc. Это само по себе было удачным решением: реализация libgcc выиграла от большой работы по снижению конфликтов блокировок, что важно при масштабировании до больших виртуальных машин.
Мы также добавили в GNU libunwind самодостаточный воспроизводимый пример и исправление (открывается в новом окне) и убедились, что у других программ для размотки стека подобной проблемы нет.
Эффективность диагностики на уровне популяции
В процессе отладки мы многому научились, изучив специфические детали динамической компоновки, метаданных размотки стека DWARF, доставки сигналов Linux, ABI System V и механизма обработки исключений в C++. Но главный урок оказался проще, чем все это.
Самым важным шагом было не умелое чтение ассемблерного кода или глубокое знание деталей. Речь шла о создании высококачественного набора данных. В отсутствие этого набора данных мы смешивали два разных явления в одну историю и пытались логически разобраться в возникшей путанице. Как только у нас появились точные и полные данные о популяции, структура проблемы стала очевидной: одна популяция сбоев принадлежала к неисправному хосту, а другая — к состоянию гонки в libunwind. По мере улучшения данных отладка стала проще.
Для инфраструктурных систем, таких как Rockset, это имеет огромное значение. Это расследование подтвердило нашу приверженность глубокому мониторингу, автоматизированным расследованиям и постоянному совершенствованию наших операционных инструментов. Надежность — это не просто исправление ошибок после их возникновения, это создание данных, рабочих процессов и навыков, которые превращают неразрешимые проблемы в диагностируемые и решаемые.
Источник: openai.com
Похожие записи
- Обновленный GPT-5.5 Instant от OpenAI лучше справляется с поиском товаров, сложными ограничениями и пониманием намерений пользователя — и он уже включен в API.
- Samsung открывает доступ к ChatGPT Enterprise и Codex после снятия ограничений, связанных с искусственным интеллектом.
- ChatGPT for Teachers (самая современная модель ChatGPT 5.2) БЕСПЛАТНО. 1….
Оцените материал:
Похожие записи
МВД проговорилось об опасности банковской биометрии для россиян, но потом передумало. Почему?
09.12.2025
Маск, Nvidia и Grokipedia: ставка на новый слой ИИ-инфраструктуры
04.11.2025
Сатья Наделла утверждает, что люди активно используют искусственный интеллект Copilot от Microsoft.
30.01.2026Присоединяйтесь и подпишитесь на рассылку самых свежих новостей по Email
Получайте свежие новости и идеи на почту. Без спама — только самое интересное.
Нажимая «Подписаться», вы соглашаетесь с политикой конфиденциальности.
