Ada perbedaan besar antara membuat model pembelajaran mesin yang berfungsi di komputer Anda dan membuat model tersebut tersedia untuk digunakan orang lain. Jika penerapannya buruk, pengguna Anda akan merasa frustrasi karena perangkat lunak Anda tidak dapat diandalkan. Dan perlu waktu berbulan-bulan untuk menerapkannya dengan baik!

Artikel saya sebelumnya membagikan prinsip-prinsip yang telah saya pelajari selama bertahun-tahun. Yang ini membahas kode contoh dan mengkritik pendekatannya. Ketika saya mulai menerapkan model pembelajaran mesin ke layanan web, saya menemukan kompleksitasnya membingungkan, dan saya berharap panduan ini akan membantu pembaca dalam situasi serupa.

Saya membuat serangkaian prototipe untuk mengeksplorasi berbagai alat dan ide di MLOps untuk pengklasifikasi teks dasar. Posting ini akan fokus pada prototipe terbaru. Ini memiliki pelatihan, pengujian, dan penerapan model otomatis ke layanan web dengan AWS Lambda.

Saya membatasi jumlah alat khusus ML karena dua alasan: 1) Saya ingin menggunakan infrastruktur yang serupa dengan proyek perangkat lunak tradisional dan 2) di tempat kerja seringkali lebih mudah untuk memenuhi kebutuhan kepatuhan dengan cara ini.

Catatan tambahan: Anda mungkin memperhatikan bahwa beberapa repo ini berumur beberapa tahun. Awalnya saya melakukan ini hanya untuk diri saya sendiri. Selama bertahun-tahun saya menyadari bahwa saya sering menggunakan mereka sebagai contoh bagi orang lain dan hal itu memotivasi saya untuk akhirnya menuliskannya.

Dasar

Terkadang saya merindukan masa lalu ketika Anda meng-host kode Anda di server di bawah meja di suatu tempat! Pada masa itu, memasukkan kode atau model kita ke server berarti menyalin file dan memulai ulang daemon web.

Ada banyak masalah dengan pendekatan itu. Berikut adalah beberapa permasalahan tersebut:

  • Artinya kita harus menghabiskan lebih banyak waktu hanya untuk mengelola server, seperti menerapkan patch keamanan, memastikan internet dan listrik tidak padam, atau memperbaikinya ketika komponen perangkat keras rusak.
  • Prosesnya rentan terhadap kesalahan dan bug, seperti kode yang berfungsi di komputer Anda tetapi tidak di server. Atau Anda mungkin lupa menjalankan tes.
  • Layanan di bawah meja hanya dapat menangani begitu banyak permintaan per detik. Jika situs Anda tiba-tiba menjadi populer, situs tersebut mungkin tidak mampu mengimbangi lonjakan pengguna.

Alat-alat modern dirancang untuk mengatasi masalah ini, tetapi alat-alat tersebut dapat mempersulit pembelajaran basis kode baru.

Menyebarkan model ke Lambda

Repo ini memiliki pelatihan dan penyajian untuk model pembelajaran scikit klasifikasi berita tetapi harus berfungsi untuk PyTorch dan perpustakaan lainnya.

Katakanlah secara hipotesis bahwa kita sedang membangun layanan backend untuk Medium untuk merekomendasikan kategori yang sesuai untuk postingan baru. Frontend Medium akan mengirimkan teks artikel ke backend kami dan menerima kategori prediksi.

Saat saya membaca repo, saya akan merangkum terminologi di akhir setiap bagian.

API Gateway dan Lambda

Permintaan HTTP diterima oleh gateway API dan diteruskan ke Lambda. Jika ada pekerja Lambda yang tersedia, permintaan akan diteruskan ke pekerja tersebut. Jika tidak ada, ia akan memulai pekerja baru yang akan memuat kode, memuat model, dan menjalankan permintaan. Jika dibutuhkan lebih dari 30 detik untuk memuat kode dan model, permintaan akan gagal tetapi pada akhirnya akan boot dan permintaan akan berfungsi.

Untuk pengujian saya menggunakan permintaan POST dengan payload sederhana seperti ini:

{
    "text": "The Seattle Seahawks crushed the Cleveland Browns on Thursday 52-7"
}

Gateway API menerima permintaan dan memanggil pengendali Lambda kami dengan info permintaan di parameter.

Dalam kode Lambda, gateway API mengirimkan informasi ini dalam dua dicts event dan context. Dari melayani/app/app.py:

def lambda_handler(event, context):
    request_body = json.loads(event["body"])
    prediction = model.predict([request_body["text"]])[0]

    return {
        "statusCode": 200,
        "body": json.dumps({
            "response": prediction,
            "request": request_body
        })
    }

Gateway API mengirimkan isi permintaan POST di event[“body”] sebagai string biasa sehingga kita perlu mengurai JSON di dalamnya. Kemudian kita menjalankan bidang teks melalui model. Perhatikan bahwa kita perlu membuatnya menjadi daftar masukan (dengan satu elemen) dan kemudian mengambil prediksi pertama dari keluaran. Itu karena metode prediksi scikit-learn dirancang untuk kumpulan data. Kemudian API gateway memerlukan respon dalam dict bentuk tertentu termasuk kode status HTTP (200). Karena kebiasaan, saya juga ingin mengembalikan data permintaan untuk mempermudah proses debug.

Terminologi baru

  • AWS Lambda: Ini menjalankan kode kami di server dan menangani peningkatan jumlah pekerja secara otomatis.
  • AWS API Gateway: Ini berfungsi sebagai jembatan antara Lambda kami dan permintaan yang datang dari Internet.
  • HTTP POST: Ini adalah metode HTTP yang kami gunakan. Meskipun GET merupakan metode yang lebih sesuai secara semantik, pengiriman JSON secara umum tidak didukung dengan GET. Menurut pengalaman saya, POST lebih umum karena struktur data masukan seringkali rumit.

Docker dan Python, dipanggil oleh Lambda

Dari penyajian/aplikasi/Dockerfile:

CMD ["app.lambda_handler"]

Ini adalah baris terakhir Dockerfile, dan memberitahu Lambda untuk memanggil fungsi lambda_handler di app.py.

Saat Lambda dimulai, ia mengimpor file app.py yang menyebabkan segala sesuatu di luar fungsi dijalankan, seperti memuat model dalam kasus kita. Dengan Lambdas kami menyebutnya awal yang dingin. Kemudian tersedia hingga pekerja dimatikan. Model dimasukkan ke dalam image Docker yang sama dengan kode dan dimuat seperti file lainnya. Gambar Docker dibuat dan diterapkan dari Github Actions ketika kode digabungkan ke main yang mengubah apa pun di folder serving. Demikian pula, konfigurasi gateway API dan Lambda dapat diperbarui secara bersamaan. Salah satu file di bawah serving adalah file model itu sendiri, yang disimpan menggunakan kontrol versi data (DVC). Jika model berubah saat digabungkan ke main, image Docker akan dibuat ulang dan diterapkan ulang dengan model baru.

Di sinilah model dimuat di serve/app/app.py dengan waktu dan metrik dihilangkan:

def load_model():
    ...
    model_path = os.path.join(os.path.dirname(__file__), "data/model.joblib.gz", )
    model = joblib.load(model_path)

    ...

    return model

model = load_model()

Yang kami lakukan hanyalah memanggil joblib.load jalur file. Ini berfungsi karena model disimpan dengan joblib dan karena kode penyajian memiliki semua dependensi yang diperlukan untuk model dari scikit-learn.

Baris pertama adalah memastikan bahwa jalur file relatif terhadap app.py, bukan relatif terhadap tempat app.py dijalankan, karena saya tidak tahu apakah Lambda membuatnya konsisten. Saya melakukan itu karena kebiasaan karena saya pernah mengalami burn di masa lalu ketika kode dijalankan dari direktori yang tidak terduga.

File model tersebut dimasukkan ke dalam image Docker di serve/app/Dockerfile:

COPY data ./data

Itu hanya menyalin folder penyajian/aplikasi/data ke dalam image Docker dari mesin yang membuat image Docker. Jika karena alasan tertentu kami memerlukan model lain, kami cukup menaruhnya di folder itu dan model itu juga akan tersedia di dalam Docker.

Istilah baru

  • Cold start: Jika tidak ada pekerja Lambda yang segera tersedia, Lambda harus memulainya.
  • Docker: Docker adalah cara mengemas kode, data, dan dependensi bersama-sama sedemikian rupa sehingga dapat dijalankan dengan cara yang sama di komputer mana pun.
  • Kontrol Versi Data (DVC): Saya menggunakan DVC untuk membuat versi file besar seperti model, karena git tidak berfungsi dengan baik dengan file besar.
  • Tindakan Github: Github yang akan menjalankan kode kami di server mereka untuk waktu yang singkat, dipicu oleh perubahan di Github atau pengatur waktu. Ini gratis untuk penggunaan skala kecil.
  • cabang utama: Dalam pengembangan Github, biasanya meletakkan versi kode saat ini di cabang bernama "utama" dan membuat cabang lain sambil menulis kode baru.
  • digabung: Ketika perubahan dari satu cabang git dimasukkan ke cabang lain. Dalam hal ini, saya berbicara tentang menggabungkan kode dari cabang pengembangan ke cabang utama.

Bacaan tambahan

Membangun dan menerapkan dengan CDK dari Github Actions

Gambar Docker dibuat dan diunggah ke AWS Elastic Container Registry (ECR) di CDK dalam melayani/deployment/stacks/lambda_service.py:

handler = lambda_.DockerImageFunction(
    self,
    "ExampleTextClassifierHandler",
    code=lambda_.DockerImageCode.from_image_asset("../app"),
    timeout=cdk.Duration.seconds(60),
    memory_size=3008
)

Fungsi from_image_asset membuat image Docker untuk kita, yang mengambil direktori berisi Dockerfile yang ingin Anda buat sebagai parameter. Saya mengatur ukuran memori menjadi 3 GB yang mungkin lebih dari yang diperlukan tetapi ini akan membantu latensi karena Lambda menskalakan CPU dengan lebih banyak RAM. Batas waktu 60 detik ada di lapisan Lambda. Perlu diingat bahwa API gateway memiliki batas waktu independen dengan maksimum 30 detik.

Gambar Docker diperlukan dengan Lambda karena ukuran model dan ukuran ketergantungan Python. Kalau tidak, saya akan menggunakan penerapan file zip Lambda yang lebih cepat.

CDK adalah kerangka kerja untuk infrastruktur sebagai kode (IaC). Ini berarti kita mendefinisikan konfigurasi AWS kita dalam kode. Ini membantu kami menstandardisasi konfigurasi dan penerapan sehingga membantu mengurangi kecelakaan. Karena disimpan di repo, ini juga berarti kita dapat menggunakan proses pull request untuk melakukan peer review untuk konfigurasi kita. Ini juga berarti bahwa kami memperbarui kode layanan dan infrastruktur kami pada saat yang sama, sehingga kami dapat menggabungkan model baru dengan peningkatan memori misalnya. Dan ini juga berarti kita dapat mengembalikan perubahan konfigurasi dengan lebih mudah. Selain itu, sebagai salah satu manfaat terakhir, CDK diimplementasikan sehingga sebagian besar penerapan dilakukan dengan sedikit atau tanpa waktu henti.

Tumpukan CDK dijalankan dari .github/workflows/deploy_service.yml:

    - run: cdk deploy --require-approval never
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_DEFAULT_REGION: us-west-2

Ini menjalankan penyebaran cdk. Kita harus menonaktifkan pertanyaan persetujuan interaktif jika tidak, pertanyaan tersebut akan hang. Bagian lainnya mengatur variabel lingkungan untuk pengguna AWS IAM, disalin dari Rahasia Tindakan Github yang diatur pada repo ini (saya mengaturnya secara manual). Kemudian ketika CDK membuat gateway API Anda, CDK akan menampilkan URL yang dibuat secara otomatis di Github Actions.

Lebih jauh lagi di deploy_service.yaml, kami menginstal DVC dan mengambil model:

    - run: pip install dvc[s3]
    - run: dvc pull
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Ini juga memerlukan kredensial AWS untuk membaca dari bucket S3.

Di bagian paling atas file, kita dapat melihat kapan alur kerja Github Action dipicu:

on:
  push:
    branches:
      - main
    paths:
    - 'serving/**'
    - '.github/workflows/deploy_service.yml'

Ini menjalankan tindakan deploy_service.yaml ketika maindiperbarui (yang terjadi pada penggabungan PR) DAN ketika apa pun di bawah serve/ diubah, atau deploy_service.yaml itu sendiri diubah.

Istilah baru

  • AWS Elastic Container Registry (ECR): Tempat untuk mengunggah gambar Docker di AWS untuk digunakan di Lambda dan layanan AWS lainnya.
  • Infrastruktur sebagai kode (IaC): Mendefinisikan infrastruktur Anda dengan kode, bukan mengklik tombol di antarmuka pengguna.
  • AWS Cloud Development Kit (CDK): Alat dan pustaka AWS untuk infrastruktur sebagai kode yang dapat ditentukan dalam Javascript atau Python.
  • AWS S3: Penyimpanan file di AWS.
  • Pengguna AWS IAM: Akun untuk komputer atau orang yang dapat mengambil tindakan di akun AWS Anda sesuai dengan izin terkait. Biasanya diakses melalui kunci akses dan rahasia. Kunci akses dan rahasia dianalogikan dengan nama pengguna dan kata sandi tetapi untuk akses terprogram.
  • Rahasia Tindakan Github: Konfigurasi sensitif yang disimpan di Github terkait dengan repo, dan tersedia untuk Tindakan Github, seperti kunci akses atau kata sandi.
  • Alur kerja Github Action: Serangkaian langkah untuk menjalankan Github Actions, terkait dengan pemicu seperti perubahan cabang. Ada satu file yaml per alur kerja.
  • Permintaan tarik (PR): Di Github, pengembangan pada cabang baru adalah hal yang umum, lalu mengirimkan permintaan tarik (PR) yang merupakan permintaan untuk menggabungkan kode ke cabang lain (biasanya utama). Biasanya rekan meninjau kode yang dimodifikasi di PR sebelum menyetujui atau meminta perubahan. Biasanya juga perusahaan membatasi repo Github mereka sehingga permintaan penarikan tidak dapat digabungkan sampai seseorang menyetujuinya.

Bacaan tambahan

Pengujian layanan otomatis dijalankan dari Github Actions

Kami tidak ingin menerapkan kode yang buruk, jadi kami juga memiliki pengujian otomatis.

Pengujian yang saya lakukan sangat mendasar: 1) Uji apakah titik akhir dapat berjalan tanpa mogok 2) Uji apakah titik akhir mogok seperti yang diharapkan jika teks masukan hilang. Itu ada di serve/tests/test_lambda_handler.py:

class BasicTests(unittest.TestCase):
    def test_basic_path(self):
        lambda_handler({"body": json.dumps({"text": "example input"})}, None)

    def test_crash(self):
        self.assertRaises(BaseException, lambda_handler, {"body": json.dumps({"not_the_right_one": "example input"})}, None)

Dalam pengujian ini saya memanggil lambda_handler secara langsung tanpa set konteks, karena fungsinya tetap tidak menggunakannya.

Mereka dipicu dari .github/workflows/test_service.yml:

      - name: Run tests
        run: make test-service

"make test-service" didefinisikan di Makefile di root repo:

test-service:
   PYTHONPATH=serving/app/ python serving/tests/test_lambda_handler.py

Ini hanya menjalankan perintah python untuk menjalankan pengujian dan memastikan bahwa kode aplikasi dapat diimpor ke dalam kode pengujian. Aturan Makefile tidak terlalu diperlukan untuk itu, tapi saya suka melakukannya sehingga saya menjalankan perintah pengujian yang sama persis di mana pun: Baik di Github Actions untuk pengujian otomatis dan juga di komputer saya untuk pengujian lokal.

Tindakan Github dipicu di bagian atas test_service.yml:

on:
  push:
    branches-ignore:
      - main
    paths:
    - 'serving/**'
    - '.github/workflows/test_service.yml'

Ini diterjemahkan menjadi "jalankan tindakan ini kapan saja kode didorong ke cabang kecuali main, yang memiliki perubahan dalam penayangan atau peluang untuk test_service.yml". Ini akan berjalan pada permintaan tarik dan juga cabang non-PR. Github dikonfigurasikan sehingga jika pengujian gagal, Github akan memblokir PR apa pun untuk cabang tersebut.

Pengujian dijalankan jika ada perubahan pada serving dalam permintaan penarikan. Itu termasuk perubahan pada file model!

Melatih ulang model dari Github Actions

Sekarang mari kita lihat bagaimana model tersebut dilatih. Bagian penting dari pelatihan ada di training/src/main.py:

model = make_pipeline(
    TfidfVectorizer(min_df=30, ngram_range=(1, 2), sublinear_tf=True),
    LogisticRegressionCV()
)

model.fit(training_data.data, training_data.target)

joblib.dump(model, os.path.join(args.output_dir, "model.joblib.gz"), compress=9)

Ini adalah model bigram yang menggunakan regresi logistik dengan validasi silang untuk mengoptimalkan bobot regularisasi. Pengaturan min_df mengabaikan ngram yang jarang terjadi sehingga membantu kita menjaga model tetap kecil tanpa kehilangan banyak akurasi. Sublinear_tf mengurangi dampak pengulangan ngram yang sama, dan menurut saya hal itu membuat model sedikit lebih kuat terhadap masukan yang aneh.

Saya menggunakan helper make_pipeline dari scikit-learn untuk membuat objek Pipeline. Saya menemukan bahwa saluran pipa scikit-learn 1) mempermudah pemuatan dan penyimpanan 2) membuat penyetelan hyperparameter lebih efektif 3) mengurangi kebocoran data pengujian yang tidak disengaja ke dalam pelatihan.

Jika Anda melihat main.py, Anda juga akan melihat evaluasi model dan perbandingan terhadap garis dasar.

main.py dipanggil dari Makefile dari perintah DVC:

train:
   dvc repro train

Seperti sebelumnya, saya suka menggunakan Makefile untuk memastikan bahwa saya menjalankan perintah yang sama di mesin lokal dan di server. Dalam contoh ini, ia memanggil DVC untuk mereproduksi pipeline “train”, yang didefinisikan dalam dvc.yaml:

stages:
  train:
    cmd: python training/src/main.py serving/app/data/ serving/app/requirements.txt
    deps:
    - training/
    outs:
    - serving/app/data/model.joblib.gz
    metrics:
    - serving/app/data/metrics.json

Ini adalah fitur pipeline DVC dan mirip dengan make hanya saja fitur ini juga secara otomatis menjalankan dvc add pada file output kami untuk memastikan bahwa file tersebut didorong ke S3 pada dorongan dvc. Bagian metrik menyimpan metrik dalam file yang ditentukan dan perintah repro akan menampilkan nilai yang diperbarui kepada kami.

Pelatihan dipicu dari Tindakan Github di .github/workflows/train_model.yml:

# build when a branch other than main changes training or this file:
on:
  push:
    branches-ignore:
      - main
    paths:
    - 'training/**'
    - '.github/workflows/train_model.yml'

jobs:
  train:
    runs-on: ubuntu-latest
    steps:
      # ... for full steps see github ...
      # train the model
      - name: Train model
        run: make train
      # run the web service tests to make sure it still works!
      - run: pip install -r serving/app/requirements.txt
      - name: Run web service tests
        run: make test-service
      # commit the model, which needs the IAM user to access S3 on dvc push
      - name: Commit model
        # email address from https://github.community/t/github-actions-bot-email-address/17204/5
        run: |
          git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git commit -am "Automated model build"
          dvc push
          git push
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Dalam contoh ini saya berkomentar secara inline dan memotong beberapa kode boilerplate. Saya menjalankan pengujian layanan web sebelum memasukkan model ke repo karena 1) Tindakan ini tidak dapat memicu tindakan test_service dan 2) Saya tidak ingin melakukan model yang gagal dalam pengujian.

Mengonfigurasi AWS untuk Tindakan DVC dan Github

Saya menyiapkan DVC untuk menggunakan bucket S3 untuk penyimpanan, yang dikelola oleh kode Terraform di bawah infrastruktur. Ini menciptakan bucket S3 untuk digunakan DVC serta pengguna IAM untuk Github Actions yang digunakan untuk mengakses DVC dan menyebarkan CDK.

Kode ini bertele-tele di beberapa bagian tetapi saya akan memberikan gambaran umum tentang infrastruktur/resources.tf dengan komentar yang ditambahkan:

// create the bucket. In this file it's referenced as aws_s3_bucket.b.bucket
resource "aws_s3_bucket" "b" {
  bucket_prefix = "trnka-dvc-"
  acl = "private"
}

// create the IAM user for Github Actions to use
resource "aws_iam_user" "github_actions" {
  name = "github_actions_lambda_ml"
  force_destroy = true
}

// give the IAM user permissions to list files in the bucket and read/write/delete files
resource "aws_iam_user_policy" "dvc_policy" {
  name = "dvc_policy"
  user = aws_iam_user.github_actions.name

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::${aws_s3_bucket.b.bucket}"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": ["arn:aws:s3:::${aws_s3_bucket.b.bucket}/*"]
    }
  ]
}
EOF
}

// this policy is long but ensures the IAM user can run CDK and upload ECR images
resource "aws_iam_user_policy" "cdk_policy" {
  name = "cdk_policy"
  user = aws_iam_user.github_actions.name

  // ...
}

// make sure there's an access key
resource "aws_iam_access_key" "github_actions" {
  user = aws_iam_user.github_actions.name
}

// when we run terraform apply, it'll show the secret on the command line
// which we can copy into Github Actions Secrets
output "secret" {
  value = aws_iam_access_key.github_actions.secret
}

Kode ini membuat bucket S3 untuk DVC, pengguna IAM untuk Github Actions, memberikan izin yang sesuai kepada pengguna IAM, dan membuat access key sehingga Github Actions dapat “masuk” ke AWS.

Salah satu manfaat infrastruktur sebagai kode adalah kita dapat meminta para ahli untuk meninjaunya sebelum kita menjalankannya.

Istilah baru

  • Terraform: Terraform adalah alat infrastruktur sebagai kode yang bekerja dengan banyak penyedia cloud populer.
  • Kebijakan AWS IAM: Saya bukan yang terbaik dalam menjelaskan hal ini, tetapi ini adalah daftar izin yang dapat dilampirkan ke pengguna atau peran IAM.
  • Peran AWS IAM: Ini seperti pengguna IAM tetapi Anda tidak dapat masuk sebagai pengguna IAM. Itu hanya ada di sana untuk digunakan oleh layanan. Jika Anda seumuran dengan saya, Anda mungkin menganggap ini sebagai pengguna layanan dengan keamanan yang lebih baik.

Apa yang mungkin salah? Sebuah kritik

Sulit untuk meninjau karya Anda sendiri, tetapi saya akan memberikan kesempatan yang adil! Saya memiliki tiga bagian:

  1. Tinjauan terhadap prinsip-prinsip umum
  2. Ikhtisar kebutuhan industri yang tidak disebutkan sebelumnya
  3. Diskusi tentang prioritas yang harus diperbaiki

Prinsip-prinsip umum

Apakah layanan mengelola latensi vs biaya dengan baik?

  • AWS Lambda meningkatkan dan menurunkan skala secara otomatis, sehingga jika ada lonjakan pengguna, AWS Lambda akan menangani hal tersebut dan kemudian menurunkan skala untuk menghemat biaya setelahnya. Tidak memerlukan konfigurasi khusus untuk melakukan ini.
  • Saat Lambda meningkatkan skala dan menghangatkan “instance” baru, Lambda perlu mengunduh image Docker dan memuat modelnya. Kami menyebutnya awal yang dingin. Jika gambar dan model berukuran cukup besar, waktu permintaan API akan habis saat dimuat. Hal ini sangat buruk di repo kami karena tidak ada pengguna yang aktif, sehingga konkurensinya akan berada pada 0 konkurensi hingga permintaan dibuat selama pengujian, lalu habis waktu saat memuat model hingga ada Lambda yang hangat.
  • Mengenai topik biaya, Lambda akan ditingkatkan sesuai kebutuhan dengan permintaan masuk hingga batas konkurensi seluruh wilayah. Jadi seseorang dapat mengirim spam ke API Anda untuk meningkatkan Lambda Anda dan meningkatkan tagihan AWS Anda secara signifikan.
  • Repo diimplementasikan seluruhnya di us-west-2. Jika API dipanggil dari seluruh dunia atau bahkan di seluruh AS, banyak pengguna akan mendapatkan respons yang lambat hanya karena mereka jauh dari server. Sebaliknya, jika hanya dipanggil oleh server atau pengguna di dekat kita-barat-2 tidak masalah.

Apakah layanan ini sangat tersedia?

  • Karena cold start, saat layanan disebarkan, layanan tersebut menyebabkan pemadaman total hingga model baru dimuat. Dalam situasi profesional, hal ini tidak dapat diterima tetapi mungkin dapat diterima untuk pekerjaan penghobi.
  • Bisakah kita kembali dengan cepat dan aman jika ada bug yang masuk ke produksi? Ya, dalam repo ini kami akan mengembalikannya dengan menggabungkan PR di Github. Alur penerapan memerlukan waktu 4–5 menit. Mungkin perlu waktu lebih lama agar PR Anda disetujui.
  • Bisakah kita mendeteksi masalah produksi dengan cepat? Tidak, tidak ada alarm yang diterapkan di repo ini, jadi jika alarm mati, kami mungkin tidak akan langsung mengetahuinya. AWS menyediakan beberapa dasbor default untuk API gateway dan Lambda.
  • Bisakah kita beralih ke PyTorch tanpa downtime? Ya, repo ini diterapkan sehingga kami dapat membuat perubahan pembelajaran mesin yang besar seperti mengganti kerangka kerja, mengujinya, dan menerapkannya tanpa waktu henti dan dengan kemampuan untuk mengembalikannya, selama perubahan kerangka kerja tersebut merupakan PR tunggal.

Apakah ada cara agar karya yang belum teruji dapat diterapkan?

Ada beberapa kesenjangan dalam pengujian saya:

  • Komponen antara permintaan API dan Lambda belum diuji: Kemasan Docker, interaksi antara kode Lambda dan gateway API, dan konfigurasi gateway API. Bagian Docker dapat diuji dengan mudah dengan menjalankan pengujian unit di dalam Docker, meskipun ini akan sedikit memperlambat pengujian. Konfigurasi AWS dapat diuji dengan lebih baik dengan menggunakan AWS SAM untuk menjalankan tumpukan versi lokal untuk pengujian. Masalah ini juga mempengaruhi produktivitas pengembang, misalnya jika pengembang mengubah cara paket dependensi, mereka mungkin perlu mengedit Dockerfile dan sebaiknya mereka mengujinya di komputer mereka.
  • Kesenjangan lainnya adalah berdasarkan waktu: Dependensi untuk pengujian unit diinstal pada waktu yang berbeda dari dependensi untuk image Docker. Karena dependensi tidak sepenuhnya disematkan (lihat postingan sebelumnya untuk mengetahui alasannya), itu berarti bahwa secara teori versi perpustakaan baru dapat diterapkan antara pengujian dan penerapan yang dapat menyebabkan penghentian produksi. Salah satu cara untuk mengatasi hal ini adalah dengan membangun image Docker di pipeline PR, menggunakannya untuk pengujian unit, lalu mengunggah ke ECR. Hal ini juga akan menghemat waktu dalam penerapannya. Alternatif lain adalah dengan menyematkan lebih banyak dependensi.
  • Secara teori, seseorang dapat menimpa file model di S3 dan melewati otomatisasi pengujian kami, meskipun hal ini akan mengganggu jika dilakukan. Jika mereka melakukan hal tersebut, hal tersebut tidak akan benar-benar memicu penerapan karena hal tersebut tidak akan menyebabkan perubahan di Github. Jalur filenya harus sama persis di S3.

Apakah mudah bagi pengembang baru untuk mempelajarinya?

Sulit untuk menilai pekerjaan Anda sendiri dari segi kemampuan belajarnya. Jika memungkinkan, lebih baik mintalah seseorang meninjau pekerjaan tersebut dan menjelaskan tantangan yang mereka hadapi.

  • Menurut saya, ini sulit dipelajari oleh seorang pemula karena alatnya sangat banyak. Sebagian besar kerumitan terjadi pada otomatisasi, yang mungkin tidak perlu dipahami sepenuhnya oleh pengembang. Namun DVC misalnya memperkenalkan konsep baru — alih-alih hanya melakukan git clone mereka juga perlu menarik file DVC agar repo berfungsi. Itu bisa diperbaiki dengan git hooks.
  • Tantangan terkait dengan DVC adalah bahwa DVC memerlukan kredensial AWS — sekarang pengembang Anda harus memiliki pengaturan AWS agar dapat bekerja di repo, dan Anda perlu memastikan bahwa mereka memiliki izin yang sesuai ke bucket S3.
  • Untuk pengembang dengan pengalaman AWS sebelumnya, hal ini mungkin lebih mudah dipelajari dibandingkan repo dengan banyak alat atau platform khusus ML.
  • Terraform menambahkan bahasa pemrograman dan cara berpikir lain. Jika saya bisa melakukannya lagi, saya akan mencoba mengganti bagian itu dengan CDK sehingga pengembang tidak perlu mempelajari bahasa infrastruktur lain.
  • API gateway dan Lambda mungkin sulit untuk dipahami, dan menurut pengalaman saya, ini bukanlah teknologi yang bagus untuk pengembang junior.
  • Penggunaan "dvc repro" mungkin tidak sepadan dengan usaha untuk mempelajarinya.
  • Juga menurut pendapat saya itu tidak cukup terdokumentasi untuk seorang pengembang junior.
  • Menurut saya, parameter pengendali Lambda sulit dipelajari, karena tidak ada pelengkapan otomatis untuk struktur datanya. Lambda Powertools dapat membantu dalam hal ini, tetapi menambah ketergantungan lain.

Apakah pengembang akan menghadapi kesulitan besar saat menggunakan repo ini?

Meskipun subbagian sebelumnya membahas tentang pengembang baru, subbagian ini membahas pengalaman sehari-hari pengembang berpengalaman.

  • Apakah ada situasi yang mungkin memerlukan banyak PR di seluruh repo? Pelatihan, pelayanan, dan infrastruktur semuanya berada dalam repo yang sama sehingga perubahan besar dapat dilakukan dalam satu PR yang telah teruji dengan baik. Di sisi lain, kemungkinan besar kode ini dipanggil oleh repo lain yang tidak ditampilkan, dan perubahan yang melibatkan bentuk masukan JSON, misalnya, mungkin memerlukan PR di seluruh repo.
  • Apakah repo ini akan berfungsi baik dengan pemindai keamanan seperti depandabot? Dulu saya mengalami tantangan dalam mengupdate paket ML yang ditandai oleh dependabot dengan mudah. Dengan pengaturan repo ini, Anda dapat mengonfigurasi dependabot untuk membuka PR dengan dependensi baru, membuat model yang diperbarui, dan menguji apakah model tersebut berfungsi di layanan. Jika PR lolos, kita cukup menggabungkannya untuk memperbarui dependensi.
  • Meskipun penerapan memerlukan waktu 4–5 menit, akan lebih baik jika penerapannya mendekati 1 menit. Untungnya Github Actions memberi kami waktu untuk melacak secara bertahap sehingga kami dapat menyelidiki lebih lanjut. Misalnya, diperlukan waktu lebih dari 1 menit hanya untuk menginstal DVC dan CDK setiap saat. Menjalankan pipeline CDK termasuk build Docker membutuhkan waktu sekitar 3 menit.

Ada masalah keamanan?

Saya bukan seorang profesional keamanan tetapi saya telah terlibat dalam cukup banyak tinjauan untuk memeriksa dasar-dasarnya.

  • Keamanan IAM untuk peran Lambda? CDK secara otomatis membuat IAM role minimal untuk kita. Bahkan jika penyerang menemukan cara untuk menjalankan kode arbitrer di dalam Lambda kami, peran tersebut tidak memiliki izin untuk berbuat banyak.
  • Keamanan IAM untuk pengguna Github Actions? Pengguna ini memiliki izin yang cukup dan penyerang dapat melakukan kerugian jika mereka memiliki akses. Rahasia Tindakan Github cukup aman. Masalah umum terbesarnya adalah saya harus merotasi kredensial IAM secara manual jika bocor.
  • Jika Anda mengadaptasi ini untuk bekerja dengan data pelatihan sensitif, Github Actions mungkin bukan pilihan.
  • API ini sepenuhnya terbuka untuk dunia. Jika modelnya sangat unik dan berharga, Anda tentu tidak ingin orang lain menyelidikinya tanpa pandang bulu. Pemfilteran autentikasi dan/atau IP dapat membantu.
  • file acar memungkinkan eksekusi kode arbitrer. Namun untuk menulis file acar tersebut, seseorang memerlukan akses tulis ke bucket S3 Anda dan perlu memicu penerapan ulang dari Github. File acar bukanlah vektor serangan pilihan dalam sistem ini.
  • Jika ada kerentanan pada image Docker, maka kerentanan tersebut tidak akan diperbarui hingga penerapan berikutnya. Ini adalah salah satu kelemahan menggunakan Docker untuk lambda — kamilah yang mengelola dependensi sistem ini, bukan AWS yang mengelolanya seperti yang mereka lakukan untuk lambda file zip.

Kekhawatiran di perusahaan yang sebenarnya

Jika Anda membangun sistem secara profesional, repo ini tidak cukup sebagai template. Ini adalah daftar pertimbangan yang saya miliki di industri:

  • Keamanan titik akhir (autentikasi, manajemen rahasia, pembatasan IP, dll)
  • Stabilitas titik akhir (DNS)
  • Pembuatan versi titik akhir — Jika struktur data input atau output akan berubah sedemikian rupa sehingga tidak kompatibel, Anda mungkin perlu mendukung beberapa struktur data dan memiliki versi titik akhir itu sendiri.
  • CORS saat diintegrasikan dengan frontend yang dilayani dari domain berbeda, dan juga pengoptimalan latensi terkait CORS
  • PyPI pribadi di dalam Docker, jika Anda menggunakan modul internal apa pun
  • Beberapa tahapan penerapan: dev, staging, prod
  • Pengujian integrasi, pengujian ujung ke ujung, dan pengujian unit untuk logika bisnis apa pun
  • Konkurensi yang disediakan, disiapkan untuk menghangatkan Lambda sebelum gateway API beralih ke versi baru sehingga tidak ada waktu henti
  • Dasbor dan alarm
  • Melatih pembuatan versi data, pembaruan data, dan pengujian data — Jika Anda mendapatkan data baru dari waktu ke waktu, diperlukan alur keseluruhan sebelum pelatihan.

Sekarang saya sedang memikirkan tentang bisnis ini, inilah saat yang tepat untuk menyebutkan gambaran besarnya. Saat Anda menulis API, itu adalah bagian dari sistem yang lebih besar. Saya telah melihat kerugian yang signifikan akibat terlalu fokus pada satu repo dan terlalu sedikit pada kode yang memanggil API. Misalnya, kami dapat mengubah API kami tanpa memberi tahu siapa pun dan hal ini dapat menurunkan produk kami.

Demikian pula, ada baiknya untuk mempertimbangkan pengalaman pengembang dalam menggunakan API kami, seperti apakah API tersebut mudah dipelajari dan digunakan. Orang lain telah membahas topik pengalaman pengembang untuk API lebih baik daripada saya:

Jika Anda menerapkan layanan ke akun AWS yang sama dengan layanan lainnya, ada beberapa pertimbangan tambahan:

  • Apakah layanan diberi nama dan diberi tag sehingga Anda dapat mengaudit tagihan AWS Anda dengan mudah? Saya menyarankan setidaknya menandai semua sumber daya dengan tim yang memproduksinya.
  • Jika Anda tidak memiliki batas konkurensi Lambda, ingatlah bahwa semua Lambda di akun dan wilayah yang sama memiliki batas konkurensi yang sama. Jadi jika Lambda Anda menggunakan konkurensi 999 dan batasnya adalah 1000, hanya 1 Lambda lain di perusahaan yang dapat berjalan pada satu waktu dan yang lainnya akan dibatasi. (Saya mengatakan ini dari pengalaman nyata produksi Lambdas yang dibatasi karena layanan logging menghabiskan konkurensi kami.)

Prioritas yang harus diperbaiki

Saya membahas banyak masalah dengan contoh repo ini. Dalam industri, kita sering kali tidak bisa memperbaiki semua hal yang kita inginkan, setidaknya tidak dalam waktu dekat. Jadi kita harus memprioritaskan apa yang harus diperbaiki terlebih dahulu.

Prioritas harus dipandu oleh kebutuhan bisnis, kebutuhan pengguna, dan bagaimana layanan Anda diintegrasikan ke dalam produk. Jika ini adalah layanan penting yang menangani data pribadi, hal ini sangat berbeda dengan layanan bagus yang tidak dapat menurunkan produk. Jika itu adalah API yang digunakan oleh pengembang eksternal, itu sangat berbeda dengan API internal. Jika itu adalah layanan yang mendukung 100.000 permintaan per jam, itu berbeda dengan 100 permintaan per jam. Dan seterusnya.

Berikut ini adalah contoh penentuan prioritas yang mungkin masuk akal untuk startup kecil:

Prioritas tinggi

  • Nama domain, sehingga kami tidak menyebabkan gangguan secara tidak sengaja saat kami mengubah gateway API
  • Konkurensi yang disediakan sehingga mengurangi waktu henti saat menerapkan pembaruan
  • Bicaralah dengan orang yang mengintegrasikan API jika bukan Anda!
  • Kepatuhan apa pun yang diperlukan

Prioritas menengah

  • Pemfilteran IP, autentikasi, dan/atau keamanan pilihan Anda — Kisarannya bisa tinggi hingga rendah tergantung pada aplikasinya
  • Alarm untuk pemadaman listrik — Kisarannya bisa tinggi hingga rendah tergantung pada aplikasinya
  • Penerapan yang benar-benar tanpa waktu henti

Prioritas rendah

  • Yang lainnya

Alternatif

Saya juga mencoba beberapa alternatif dan saya akan menyebutkan secara singkat apa yang saya temukan.

Menerapkan ke AWS ECS

AWS Elastic Container Service (ECS) adalah pilihan yang lebih baik daripada Lambda bagi kebanyakan orang. Itu selalu aktif, jadi Anda tidak perlu khawatir tentang waktu yang diperlukan untuk memuat model Anda saat booting. Kerugian ECS adalah sulitnya melakukan penyiapan penskalaan otomatis dengan benar dan biayanya bisa lebih mahal.

Terapkan dengan Cortex.dev

Saya menemukan Cortex.dev sangat cepat dan mudah. Penerapannya sendiri jauh lebih cepat dari apa pun, dan tidak lebih rumit dari Heroku. Saya ingat saat itu saya berpikir bahwa tim keamanan saya mungkin tidak menyukai beberapa aspek penerapannya.

Pembuatan versi model dengan git-lfs di Github

Saya mulai menggunakan git-lfs untuk pembuatan versi model dan tingkat gratis Heroku untuk penerapan. Saya penasaran untuk menggunakan Github sebagai penyedia git-lfs setelah kesulitan menemukan alternatif di tempat kerja.

Baiklah, saya mengetahui mengapa kami tidak menggunakan git-lfs Github! Ketika Anda mencapai kuota LFS, repo Anda akan dikunci hingga Anda membeli lebih banyak kuota atau menghapus repo. Terakhir saya periksa, Github tidak memiliki cara untuk meningkatkan kuota Anda secara otomatis sehingga Anda mengandalkan orang-orang yang melakukan peningkatan secara manual di situs web secara berkala.

Hal-hal yang tidak saya dapatkan

Ada begitu banyak alat MLOps saat ini! Untuk setiap perpustakaan, alat, atau platform yang saya gunakan, ada lusinan alternatif. Saya telah mendengar hal-hal baik tentang kubeflow misalnya dan mungkin ini saat yang tepat untuk mengevaluasi kembali Sagemaker.

Saya juga melihat beberapa orang menerapkan model dengan kode menggunakan kemasan Python. Sepertinya itu cara yang bagus untuk mengemas kode, data, dan dependensi secara bersamaan.

Kesimpulan

Saya harap saya dapat memperjelas konsep umum dalam layanan web pembelajaran mesin!

Meskipun saya menggunakan AWS Lambda dalam contoh ini, hal ini terutama karena rasa ingin tahu. Harap jangan menafsirkan postingan ini sebagai dukungan Lambda untuk layanan pembelajaran mesin. Apakah Lambda merupakan pilihan yang baik akan bergantung pada kebutuhan Anda.

Perlu diingat juga bahwa ini mungkin berfungsi sebagai panduan untuk startup kecil, tetapi saya ragu apakah ini siap untuk beroperasi dalam skala yang lebih besar. Dan saya pasti mengabaikan beberapa bagian dari pekerjaan MLOps yang umum, seperti dukungan untuk eksperimen.

Terima kasih sudah membaca! Jika ada bagian artikel yang Anda anggap membingungkan, beri tahu saya.