Този блог е написан от Lanqi Fei, старши ML учен в Glassdoor, Jamie Broomall, старши софтуерен инженер в WhyLabs, и Наталия Skaczkowska-Drabczyk, учен по данни за успеха на клиентите в WhyLabs.

Предизвикателството на латентността на интеграцията

Помислете за сценария, при който искаме да интегрираме нов инструмент в съществуваща услуга, която потенциално работи в реално време и включва някакъв потребителски интерфейс. Трябва да сме сигурни, че забавянето на услугата в производството е приемливо след интеграцията, като същевременно поддържаме ниски общите разходи за поддръжка. При този сценарий трябва да се направят компромиси и правилният избор ще зависи от индивидуалните характеристики на услугата и новоинтегрираната функция. Опростяването на тази функция е обичаен начин за получаване на значително предимство в тази игра за оптимизация.

Този блог, написан в сътрудничество между Glassdoor и WhyLabs, описва пример от реалния свят на предизвикателство за латентност на интеграция и дава подробен преглед на промените, приложени в whylogs (библиотека за регистриране на данни с отворен код, поддържана от WhyLabs) за смекчаването му .

Кои са най-добрите опции за намаляване на латентността?

Има няколко опции за намаляване на забавянето при интегриране на нова функция в съществуваща услуга.

Преструктуриране на вашата услуга или архитектура, за да позволите ранен отговор на повикващия, преди да извършите допълнителната работа (това може да е толкова просто, колкото използването на модел на асинхронно извикване с оператор на журнал или толкова сложно, колкото DAG рамка). Можете да мислите за услугата си като графика - нейните възли трябва да са критичните за латентността задачи и в идеалния случай те трябва да се изпълняват, инструментират и тестват независимо.

  • Използването на DAG може да бъде добър начин за мащабиране на услуга до голям брой нови функции и интеграции, като същевременно се поддържат изискванията за латентност и се управлява сложността на критичния път за генериране на висококачествен потребителски отговор. Недостатъкът на този подход е допълнителната сложност, както и разходите за разработка и поддръжка.
  • Започването с модел на асинхронно повикване може да бъде достатъчно и подходящата първа стъпка за прости сценарии и е много по-евтино за настройка.

Без преструктуриране на вашата услуга и заплащане на допълнителните разходи за забавяне или изчислителни ресурси. Ако вашата услуга е сравнително проста, можете да интегрирате новата функция в същия линеен път на изпълнение със заявката за услуга. Това има недостатъка, че всяко ново извикване на функция добавя известно време към общото забавяне и разходите за изпълнение на услуга. Този подход може да бъде най-рентабилният път, ако услугата ви вече е достатъчно бърза, за да си позволи допълнителната латентност или ако можете да увеличите хардуерните ресурси, за да поемете допълнителните разходи (ако е необходимо) за времето за изпълнение на новата функция.

Оптимизиране на интегрираната функция. Ако функцията, която интегрирате, е с отворен код, анализирането на нейното внедряване може да отвори потенциални пътища за оптимизиране. Можете да промените обхвата на функцията само до основни операции или да персонализирате потока от данни по начин, който ще обслужва по-добре вашия случай на употреба. В този казус обхващаме точно този подход.

Казус от Glassdoor

Glassdoor инвестира в подобряване на своите възможности за наблюдение на ML модела. По-конкретно, те започнаха да използват платформата за наблюдение на WhyLabs и работят върху внедряването на всички свои различни ML модели там. Като се има предвид, че Glassdoor е признат за своята прозрачност на заплатите, един от най-важните модели, които искат да наблюдават, е моделът за оценка на заплатите.

Моделът за оценка на заплатата на Glassdoor е регресионен модел, който прогнозира средната стойност и диапазона на заплатата при дадена длъжност, местоположение и работодател. Интегрирането на whylogs (компонента с отворен код на стека за наблюдение на WhyLabs) в тази услуга ще даде възможност за улавяне на потенциално отклонение на данните в прогнозите, откриване на аномалии във входните данни на модела и по-добро разбиране на потребителския трафик.

Едно от предизвикателствата, пред които се изправи Glassdoor, докато се интегрира с WhyLabs, беше изпълнението на строгите изисквания за латентност, наложени от природата на услугата за оценка на заплатите в реално време. По-конкретно, увеличението на латентността поради интегрирането на whylogs не трябва да надвишава 30 милисекунди. Въпреки това, в ранната фаза на интеграцията, екипът установи, че тази стойност е 60 милисекунди. След като разгледаха няколко различни решения на този проблем, те се обединиха с инженерите на WhyLabs в съвместни усилия да персонализират кода на whylogs за техния конкретен случай на употреба.

Резюме на решението

Библиотеката за регистриране на данни с отворен код whylogs беше оптимизирана първо за ефективност на пакетната обработка, а не за латентност на един ред. В сценария на Glassdoor разходите за преструктуриране на услугата или добавяне на асинхронна обработка не бяха практични за кратки времеви рамки. И все пак имаше полза от интегрирането на профилирането на whylogs, ако можеше да се направи в процес в рамките на услугата.

Екипът на WhyLabs прие предизвикателството и приложи две промени в whylogs:

  • Използване на директен тип инспекция върху маски от серията pandas
  • Оптимизиране на кода за сценария с единичен ред чрез обработка на данните като редове от скалари вместо единичен елемент pandas Series

Тези промени успешно намалиха забавянето, причинено от профилирането на whylogs от 68 ms на 8 ms! В следващия раздел ще намерите подробно описание на този процес на оптимизация.

Как да оптимизирате извикване на функция на Python в библиотека с отворен код

Първо, важно е да се установят критериите за успех. В този случай ние се съсредоточихме върху конкретния сценарий, който Glassdoor повдигна - регистриране на един ред в процес в услуга и поддържане на забавяне под 30 ms на повикване (започвайки от приблизително +60 ms на повикване), за да профилираме входовете с whylogs .

Второ, важно е да се създаде евтин и възпроизводим начин за измерване на латентността. За да постигнем това, ние се уверихме, че нашата тестова среда има подобни спецификации на средата на Glassdoor. Тъй като забавянето е проблем, обхванат от една нишка на изпълнение, не беше необходимо да навлизаме в колко ядра са използвани или сложността на паралелизма. Фокусирахме се върху създаването на разумен приблизителен тест за производителност за латентност с една нишка при обработка на един ред от данни с подобен размер/въведен тип, както в тестовете за латентност на Glassdoor. От това създадохме прост тест whylogs test_rolling_logger_latency_row_benchmark и го добавихме към whylogs с известно профилиране по време на изпълнение.

Използвахме cProfile, за да съберем данни за производителността по време на изпълнение за този сценарий и потърсихме тесни места. За да направи изчисленията лесни, тестът регистрира един ред от фалшиви данни (1000 пъти в първоначален прост тест за производителност, така че общото време в секунди = приблизително време в ms на извикване на функция). Можем да проведем теста за по-малко от 1 минута.

От статистиката на cProfile можете да разгледате изходните таблици, сортирани по кумулативното време на извикване на функция, или да визуализирате данните под формата на пламъчна графика (вижте по-долу).

От резултата от теста видяхме, че функциите за предварителна обработка на whylogs представляват изненадващо голяма част от общото време за регистриране на входовете (вижте функцията _pandas_split в пламъчната графика и подчертания с червено запис в профила по време на изпълнение по-горе). Стъпката за предварителна обработка на whylogs инспектира входовете и разделя стойностите по тип на партиди от стойности за ефективни актуализации, базирани на масиви; това се случва в алгоритмите на Apache dataskeches, които изчисляват статистически обобщения на входовете. Но този тип разделяне отне непропорционално дълго време при регистриране на един ред за извикване на whylogs. Когато едно извикване на whylogs оперира върху хиляди редове, стъпката на предварителна обработка се амортизира за многото редове, които се обработват, и е малка.

Разглеждайки най-скъпите функции в предварителната обработка на whylogs, видяхме, че две конфигурируеми експериментални функции са отнели около половината от времето за предварителна обработка: проверка и преброяване на броя на специалните числови стойности Inf и nan. Цялостното време, прекарано в извършване на различни проверки на isinstance, беше изненадващо голяма част от времето за профилиране на ред, дори когато whylogs считаха тази част от нашия стек за извиквания за „относително малка предварителна обработка“ стъпка преди тежките математически набори от данни, които изчисляват числени разпределения и кардиналност оценки.

Разбрахме, че можем да изключим отчитането на nan и inf по подразбиране (и да го направим функция за включване), което трябва да намали латентността приблизително наполовина; следователно ние теоретично бихме били много близо до покриване на критериите от 30 ms, след като сме започнали от около 60 ms в базовия тест.

Втората кандидатска промяна беше да се създаде нов метод за предварителна обработка за оптимизиране за специалния случай на обработка на единични редове. В тази предварителна обработка на един ред бихме променили списъка с филтри за проверка на типа в логически еквивалентно дърво, за да категоризираме и изведем типа на входна стойност - вместо да правим N проверки, очаквахме Log N проверки за стойност.

Тези две промени бяха приложени и след това проведохме отново простия тест за производителност на 1000 реда фалшиви данни, за да видим колко близо сме до постигането на нашите цели за латентност.

След като измерихме това, видяхме по-голямо от очакваното подобрение, тъй като проверките на isinstance() на всяка скаларна стойност не са същата цена като маскирането на серията pandas върху масив с дължина едно! Тръгнахме от ок. 60 ms на повикване до под 10 ms на повикване за профилиране на фиктивните данни. Това беше достатъчно по-бързо и надхвърли критериите за латентност от 30 ms на повикване.

Последната стъпка беше тестването на получената кандидатска компилация в средата на Glassdoor, за да се види дали печалбите, наблюдавани в простия тест на whylogs, се превеждат в по-реалистична настройка. Това наистина беше така, както е представено в следващия раздел.

Резултати

Таблицата по-долу показва сравнение на латентността на услугата за оценка на заплатите за една прогноза, както се съобщава от Glassdoor. Средната латентност на извода на модела без приложен мониторинг беше 32 ms, докато латентността на наблюдаваната услуга беше 100 ms при използване на версията whylogs по подразбиране. Оптимизираната версия обаче позволи много по-бързо изпълнение от 40 ms. Този резултат отговаря на строгите изисквания за латентност на услугата в реално време на Glassdoor.

Сравнявайки профилите на изпълнение на тестовия скрипт, използвайки версията по подразбиране спрямо оптимизираната версия на whylogs, можем също да видим драматично намаляване както на времето за изпълнение от 58 секунди на 2 секунди, така и значителен спад в количеството извиквания на функции от 10 милиона на 4 милиона . Оптимизираният код, когато е профилиран и визуализиран като пламъчна графика, показва напълно различна, много по-опростена структура от пламъчната графика, генерирана от неоптимизираното изпълнение на кода.

Заключение

Този казус доказва две отделни точки.

Първо, струва си да разгледате изпълнението на функцията, която се интегрира във вашата услуга. Възможно е да има някои налични настройки, които ще ускорят тази функция достатъчно, за да ви спестят болката от преструктурирането на вашата услуга или да ви спестят малко бюджет за изчисления.

Второ, това показва, че видимостта на ML може да бъде лека - въпреки богатите статистически профили, изчислени от whylogs, допълнителните разходи, въведени от профилирането, са само 8 ms на извод, като действителното време за извод е 32 ms. Това изглежда като справедлива цена за видимостта и прозренията, които WhyLabs предоставя за ML системите на Glassdoor.

Приложение

# import necessary packages
import mlflow.pyfunc
import whylogs as why
from whylogs.core import DatasetSchema
from whylogs.api.writer.whylabs import WhyLabsWriter

class MLflowModelPrediction(mlflow.pyfunc.PythonModel):
    self.why_logger = None
    ...
    def load_context(self, context: mlflow.pyfunc.PythonModelContext) -> None:
        # create a 15-minutely rolling logger
        self.why_logger = why.logger(mode="rolling", 
                                    interval=15, 
                                    schema=DatasetSchema(cache_size=1024*32), 
                                    fork=True, 
                                    when="M", 
                                    base_name="test_inference") # initiate as static instance
        self.why_logger.append_writer(writer=WhyLabsWriter())
    ...


    def predict(self, context: mlflow.pyfunc.model.PythonModelContext, 
                    model_input: pd.DataFrame) -> List[Dict[str, float]]:
        ...
        # compute predictions and store them in a list of data dictionaries
        dict_predictions = compute_predictions()
        if self.why_logger is not None:
            try:
                for p in dict_predictions:
                    self.why_logger.log(p)
            except Exception as e:
                print("Whylogs Error: {}".format(sys.exc_info()))
        ...


    def __del__(self):
        # closes the rolling logger
        if self.why_logger is not None:
            self.why_logger.close()

Първоначално публикувано на адрес https://whylabs.ai.