🚀 چرا سرویس ML شما به Rust + CatBoost نیاز دارد: راهنمای راه اندازی که واقعا کار می کند

بیایید در مورد مشکلی صحبت کنیم که مدتی است تیم های ML را آزار می دهد. هنگامی که ما با پردازش دسته ای کار می کنیم، پایتون کار را به خوبی انجام می دهد. اما سرویس در زمان واقعی؟ اینجاست که همه چیز جالب می شود.
من خودم هنگام ساخت سرویسی که باید کمتر از 50 میلی ثانیه تأخیر بماند، با این مشکل مواجه شدم. حتی بدون الزامات تأخیر دقیق، ممکن است در ابتدا همه چیز خوب به نظر برسد، اما با رشد سرویس، پایتون شروع به نشان دادن محدودیت های خود می کند. متغیرها به طور تصادفی به None تبدیل میشوند و بدون بررسی نوع، ردیابی این مسائل به یک سردرد واقعی تبدیل میشود.
در حال حاضر، ما راه حلی برای ارائه مدل بلادرنگ نداریم. تیمها اغلب به ابزارهایی مانند KServe یا BentoML روی میآورند، اما این به این معنی است که با قطعات متحرک بیشتری سروکار دارند – غلافهای بیشتری برای تماشا، تماسهای شبکه اضافی سرعت کار را کاهش میدهد.
زبان های دیگر چطور؟ C++ سریع است و تقریباً با تمام کتابخانههای ML کار میکند، اما بیایید واقعی باشیم – ساخت و نگهداری یک سرویس پشتیبان C++ چیزی نیست که اکثر تیمها میخواهند آن را انجام دهند.
اگر بتوانیم مدلهایی را در پایتون بسازیم اما آنها را با زبانی سریعتر ارائه کنیم، عالی خواهد بود. ONNX سعی می کند این را حل کند و برای شبکه های عصبی نسبتاً عالی عمل می کند. اما وقتی سعی کردم از آن با CatBoost استفاده کنم، مدیریت ویژگیهای طبقهبندی به یک چالش تبدیل شد – پشتیبانی هنوز وجود ندارد.
این ما را به Rust میآورد، که میانهی جالبی را ارائه میدهد:
- به اندازه C++ سریع است، اما به میل من آسان تر است
- سیستم نوع، منطق کسب و کار شما را تمیز و قابل پیش بینی نگه می دارد
- کامپایلر در واقع به شما کمک می کند تا به جای اینکه فقط به خطاها اشاره کنید، کد بهتری بنویسید
- اکوسیستم ML با پشتیبانی از نامهای بزرگی مانند مایکروسافت در حال رشد است
خبر خوب – در واقع یک جعبه رسمی Catboost برای Rust وجود دارد! اما قبل از اینکه خیلی هیجان زده شوید، اجازه دهید در مورد چیزهای عجیب و غریبی که در طول راه کشف کردم به شما بگویم.
بخش مشکل، خود کد Rust نیست، بلکه قرار دادن کتابخانه های زیرین C++ است. شما باید Catboost را از منبع کامپایل کنید، و ایجاد محیط مناسب برای این کار سخت ترین بخش است.
تیم Catboost تصویر مبتنی بر اوبونتو خود را برای ساختن آن از منبع ارائه میکند که به نظر عالی میرسد. اما اگر قصد دارید سرویس خود را در دبیان اجرا کنید تا همه چیز را سبک نگه دارید، چه؟ سپس بهتر است Catboost را روی همان نسخه دبیان که برای سرویس استفاده می کنید بسازید، در غیر این صورت ممکن است با مشکلات سازگاری مواجه شوید.
بیایید در مورد چرایی اهمیت این موضوع در عمل صحبت کنیم. تصویر ساخت اوبونتو برای کارکردن به 4+ گیگابایت حافظه سنگین نیاز دارد. اما اگر یک بیلد سفارشی دبیان را به درستی راه اندازی کنید، می توانید آن را به تنها 1 گیگابایت کاهش دهید. و هنگامی که خدمات زیادی را در فضای ابری اجرا می کنید، این تعداد استفاده از حافظه اضافی در صورتحساب ماهانه شما اضافه می شود.
اجازه دهید شما را در مورد راه اندازی یک محیط مبتنی بر دبیان برای Catboost راهنمایی کنم. من نه تنها توضیح خواهم داد که چه باید کرد، بلکه چرا هر مرحله اهمیت دارد.
نصب Catboost
در سمت Rust به همان سادگی با سایر جعبه ها در Rust اتفاق می افتد:
[package]
name = "MLApp"
version = "0.1.0"
edition = "2021"
[dependencies]
catboost = { git = "https://github.com/catboost/catboost", rev = "0bfdc35"}
با این حال جعبه Catboost دارای اتصالات C/C++ از پیش کامپایل شده نیست و در حین نصب (cargo build
) سعی خواهد کرد آن را از منابع مخصوص محیط شما کامپایل کند. پس بیایید محیط خود را تنظیم کنیم.
با تصویر پایه سمت راست شروع کنید
اول، ما با debian:bookworm-slim
به عنوان تصویر پایه ما چرا؟ آن را با CMake 3.24+، که ما برای فرآیند ساخت خود نیاز داریم ارائه می شود. نوع باریک اندازه تصویر ما را پایین نگه می دارد که همیشه خوب است.
راه اندازی محیط ساخت ++C
ما به یکسری بستههای ++C نیاز داریم، و در حالی که من از نسخه 16 در تنظیمات خود استفاده میکنم، شما واقعاً در اینجا انعطافپذیری دارید. هر نسخه ای که پشتیبانی می کند -mno-outline-atomics
خوب کار خواهد کرد
بیایید نصب بسته خود را به گروه های منطقی تقسیم کنیم.
راه اندازی منابع بسته
ابتدا باید منابع بسته خود را مرتب کنیم. این بخش برای به دست آوردن ابزارهای مناسب LLVM بسیار مهم است:
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y - no-install-recommends \
# will use it to download packages
wget \
# cryptographic package to verify LLVM sources
gnupg \
# check Debian version to get correct LLVM package
lsb-release \
# package management helper
software-properties-common
سپس باید مخزن LLVM را اضافه کنیم.
RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - \
&& echo "deb http://apt.llvm.org/$(lsb_release -sc)/ llvm-toolchain-$(lsb_release -sc)-16 main" \
>> /etc/apt/sources.list.d/llvm.list
مرحله اصلی نصب پکیج ها
ما به تعداد زیادی بسته نیاز داریم، و من توصیه می کنم آنها را بر اساس هدف سازماندهی کنیم – این کار تعمیر و نگهداری را بسیار آسان تر می کند:
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y - no-install-recommends \
# Basic build essentials
build-essential \
pkg-config \
# Core development packages
libssl-dev \
cmake \
ninja-build \
python3-pip \
# LLVM toolchain - version 16 works great, but any version with
# -mno-outline-atomics support will do
clang-16 \
libc++-16-dev \
libc++abi-16-dev \
lld-16 \
# Don't forget git!
git
گام بعدی برای من زمان زیادی برای فهمیدن هزینه داشت. Catboost انتظار دارد که صدای چنگک را پیدا کند /usr/bin/clang
، اما نصب ما آن را وارد می کند /usr/bin/clang-16
. به همین دلیل ما این بیت را داریم:
RUN ln -sf /usr/bin/clang-16 /usr/bin/clang && \
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
ln -sf /usr/bin/lld-16 /usr/bin/lld
و تنظیم متغیرهای محیطی را فراموش نکنید
ENV CC=/usr/bin/clang
ENV CXX=/usr/bin/clang++
ENV LIBCLANG_PATH=/usr/lib/llvm-16/lib
ENV LLVM_CONFIG_PATH=/usr/bin/llvm-config-16
مدیریت وابستگی ها
برای مدیریت وابستگی های C++ به Conan (نسخه 2.4.1+) نیاز داریم. یک نکته احتیاطی در مورد نصب:
RUN pip3 install --break-system-packages "conan==2.11.0"
که --break-system-packages
پرچم ممکن است ترسناک به نظر برسد، اما در واقع سادهترین راهی است که برای نصب بستههای پایتون در سراسر سیستم در نسخههای جدیدتر دبیان پیدا کردم. علاوه بر این، ما به هر حال از Python زیادی در ساخت تصویر خود استفاده نخواهیم کرد.
استراتژی ساخت هوشمند
در اینجا ترفندی وجود دارد که در طول مرحله توسعه فعال باعث صرفه جویی در زمان ساخت شما می شود. ساخت خود را به دو مرحله تقسیم کنید:
- ابتدا فقط وابستگی ها را بسازید:
COPY ./Cargo.* ./
RUN mkdir src && \
echo "fn main() {}" > src/main.rs && \
RUSTFLAGS="-C codegen-units=1" cargo build - release
نکته مهم اینجاست شما به آن نیاز دارید RUSTFLAGS="-C codegen-units=1"
پرچم گذاری کنید زیرا تضمین می کند که C++ و Rust با هم بازی کنند.
- سپس برنامه واقعی خود را بسازید:
COPY ./src src
RUN cargo build - release
به این ترتیب، Docker ساخت وابستگی را در حافظه پنهان نگه میدارد و تنها زمانی که کد برنامه خود را تغییر میدهد، آن را بازسازی میکنید. خیلی سریعتر!
هشدار انتقادی در مورد حافظه
این مهم است: در طی مراحل ساخت C++، به دستگاهی با 20+ گیگابایت حافظه نیاز دارید (من از 32 گیگابایت استفاده کردم). و در اینجا قسمتی است که تقریباً یک روز برای من هزینه اشکال زدایی دارد – اگر حافظه کافی نداشته باشید، یک پیام خطای واضح (یا صادقانه بگویم) دریافت نخواهید کرد. در عوض، زمان ساخت شما به طور مرموزی به پایان می رسد و شما را متعجب می کند که چه اشتباهی رخ داده است. این یکی را به سختی یاد گرفتم!
اکنون ما یک محیط Rust با Catboost داریم که میتواند همه چیزهای خوب را مدیریت کند: دستهها، دادههای متنی، جاسازیها. رسیدن به اینجا دقیقاً آسان نبود.
دفعه بعد پوشش خواهیم داد:
- ساخت وب سرویس Axum
- الگوهای بارگذاری مدل هوشمند
- ترفندهای اجرای دنیای واقعی که در طول راه یاد گرفتم
بنابراین ما این پایه را به چیزی تبدیل خواهیم کرد که در واقع می تواند به مدل ها در تولید خدمت کند! در حین فهمیدن این موضوع با مشکلات جالبی مواجه شدم، مانند بارگیری تصادفی یک مدل چندین بار برای هر تماس کنترل کننده.
و اگر این را امتحان کردید و به مشکل عجیبی برخورد کردید، به من اطلاع دهید. شنیدن اینکه دیگران با چه مشکلاتی مواجه می شوند همیشه جالب است.
فایل داکر کامل