Exécuter un job Azure ML via GitHub Actions

Avec l’arrivée du CLI et du SDK V2, Azure Machine Learning s’est orientée vers une utilisation centrée sur le MLOps, cette démarche visant à automatiser les étapes du cycle de vie d’un modèle de Machine Learning.

Plutôt que d’alourdir le code Python, ce sont maintenant des opérations décrites dans des fichiers YAML qui se chargent de l’exécution des scripts de préparation de données ou d’entrainement.

L’utilisation du CLI est en particulier efficace pour des tâches comme la création d’un compute cluster ou le déploiement d’un point de terminaison dédié à l’inférence (les prévisions, en temps réel ou en mode batch).

GitHub s’impose quant à lui comme la ressource permettant le versioning des scripts (repositories) mais aussi leur déploiement continu, grâce aux GitHub Actions.

Authentifier GitHub vis à vis du workspace Azure ML

C’est une identité de type Service Principal (SPN) qui va réaliser l’authentification de l’action GitHub vis à vis du workspace Azure ML.

Pour cela, notez le client ID lors de la création du SPN ainsi que le tenant ID et la subscription ID correspondant au déploiement du service Azure ML.

Créez ensuite un secret associé à SPN et notez sa valeur (attention, ce n’est pas le secret ID).

Structurez ces informations de la manière suivante :

{
"clientId": "xxx",
"clientSecret": "xxx",
"subscriptionId": "xxx",
"tenantId": "xxx"
}

Dans l’interface GitHub, nous allons définir un ensemble de credentials, dans le menu Settings > Secrets and variables.

Le secret doit être nommé AZURE_CREDENTIALS.

En parallèle, le SPN doit disposer d’un droit de type Contributor sur le workspace ML.

Lancer un premier job

Clonez le répertoire suivant : methodidacte/azmlcli: CLI az ML et déploiement automatisé via GitHub Actions (train & deploy model)

Celui-ci contient un fichier YAML de définition du job. Il s’agit d’un simple affichage de texte “Hello world”.

$schema: https://azuremlschemas.azureedge.net/latest/commandJob.schema.json
command: echo "hello world"
environment:
image: library/python:latest

Dans une arborescence .github/workflows, un second fichier YAML définit l’action à réaliser.

name: Deploy Azure ML Hello World

on:
  push:
    branches: [ main ]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repo
      uses: actions/checkout@v3

    - name: Login to Azure
      uses: azure/login@v1
      with:
        creds: '${{secrets.AZURE_CREDENTIALS}}'

    - name: Set up Azure CLI ML extension
      run: |
        az extension add -n ml -y
        az configure --defaults workspace=<WORKSPACE_NAME> group=<RESOURCE_GROUP_NAME>

    - name: Submit job to Azure ML
      run: |
        az ml job create -f job.yml

Pensez à bien modifier les deux noms nécessaires pour définir le workspace Azure ML et le groupe de ressources qui le contient.

Par défaut, l’action se lance automatiquement lors d’un commit ou d’un merge sur la branche main.

Contrôler la bonne exécution du job

L’action est visible dans le menu Actions.

Le statut de l’action doit indiquer “Success”.

Vérifiez en particulier la bonne authentification de GitHub vis à vis de la ressource Azure.

Dans le studio Azure ML, le job est également visible et rattaché à une experiment.

Nous retrouvons le “hello world” dans les logs du job.

Cette stratégie de déploiement continu s’appliquera sur les étapes de préparation de données, d’entrainement ou encore de déploiement du point de terminaison.

Ajouter GitHub Copilot à Visual Studio Code

Les modèles de langage ont rapidement prouvé que s’ils pouvaient exploiter le langage naturel des humains, ils pouvaient aussi être très doués pour la programmation. Les premiers modèles dédiés au code sont donc assez vite apparus et se sont tout naturellement intégrés au sein des IDE (Integrated Developement Environment). Des études ont démontré l’impact très fort sur le travail quotidien des développeurs.

Microsoft propose ainsi GitHub Copilot dans son produit Visual Studio Code. Jusqu’ici (décembre 2024), l’outil était soumis à une licence payante. Mais une annonce récente de Satya Nadella a dévoilé sa mise à disposition gratuite pour tous les utilisateurs disposant d’un compte personnel, gratuit également, sur le site GitHub.com. Nous allons voir comment activer GitHub Copilot, dans cette nouvelle version.

Vérifiez tout d’abord que vous avez reçu un email provenant de l’adresse no-reply@github.com sur votre adresse vous servant d’identifiant sur GitHub.

Cliquez sur le lien “Start using Copilot” inclus dans ce mail.

Vous recevrez alors un second email expliquant le processus pour démarrer l’outil GitHub Copilot.

L’interface GitHub Copilot apparaît alors dans un navigateur web.

Nous pouvons alors commencer à interagir avec le Copilot.

Il faut noter qu’il est possible de choisir le LLM qui sur lequel s’appuyera GitHub Copilot. Alors que la toute première version utilisait le modèle Codec d’OpenAI, nous bénéficions maintenant de l’avancée des LLM généralistes et de leur capacité à comprendre et produire du code.

Voici le cadre d’utilisation offert par Microsoft:

  • 2 000 compléments de code par mois
  • 50 messages Copilot Chat par mois (comprendre le code, refactoriser ou à déboguer un problème…)
  • Choisir entre Claude 3.5 Sonnet et OpenAI GPT-4o.
  • Apporter des modifications à plusieurs fichiers avec Copilot Edits
  • Accéder à des agents tiers conçus pour des tâches telles que l’interrogation de Stack Overflow ou la recherche sur le web avec Perplexity.

Les IDE suivants permettent l’utilisation de GitHub Copilot.

Nous retrouvons les différents paramétrages de GitHub Copilot dans le menu Settings du compte personnel GitHub, sur l’URL https://github.com/settings/copilot.

Par défaut, GitHub n’utilise pas vos données pour entrainer d’autres modèles d’intelligence artificielle.

Installer GitHub Copilot dans VS Code

Nous sommes alors redirigés vers la page de téléchargement de l’extension GitHub Copilot:

https://marketplace.visualstudio.com/items?itemName=GitHub.copilot

Cliquez ici sur le bouton “Install” qui lancera alors Visual Studio Code.

Authentifiez-vous à l’aide du bouton “Sign in to GitHub.com”. Le petit icone symbolisant Copilot est maintenant visible en bas à droite de l’écran. En cliquant sur ce bouton, nous déroulons les commandes relatives à GitHub Copilot dans la barre de recherche de l’IDE.

Quelques commandes

Même si le principe général des Copilots est d’interagir en langage naturel, il existe quelques commandes utiles pour réaliser des actions précises, précédées par un slash.

Le contexte à prendre en compte est précisé par un mot-clé précédé d’un dièse.

Copilot Edits

Copilot Edits permet de réaliser des modifications sur un ou plusieurs fichiers de l’espace de travail (workspace), sur la base d’instructions données en langage naturel.

Dans l’exemple ci-dessous, nous demandons au Copilot de créer un fichier de tests unitaires pour les fonctions développées dans le fichier passé en référence.

Un nouveau fichier apparaît alors dans l’espace de travail. Les boutons “accept” et “discard” permettent de valider ou refuser les propositions faites par Copilot.

Les assistants de programmation sont devenus en quelques mois des outils incontournables, véritablement accélérateurs pour les débutant.e.s et support du quotidien pour les plus expérimenté.e.s. Avec cette offre gratuite, Microsoft démocratise leur usage.

Se connecter à une instance de calcul Azure ML depuis Visual Studio Code

Dans un précédent article, nous explorions la procédure pour lancer simplement du code sur des machines distantes Azure ML (compute target) depuis l’environnement de développement (IDE) Visual Studio Code.

Dans les raccourcis disponibles au niveau du menu des instances de calcul, nous disposons dorénavant d’un lien permettant de lancer l’ouverture de Visual Studio Code.

Quel est l’avantage de travailler de la sorte ? Réponse : vous disposez de toute la puissance de l’IDE de Microsoft (en particulier pour les commandes Git), sans toutefois être dépendant de votre poste local.

Vous ne pouvez bien sûr utiliser qu’une instance qui vous est personnellement assignée.

Cliquez sur le lien VS Code et votre navigateur lèvera tout d’abord une alerte. Cochez la case “Toujours autoriser…” pour ne plus voir cette pop-up.

C’est maintenant au tour de VSC de faire une demande d’autorisation avant de lancer la connexion à distance.

En tâche de fond, nous assistons à l’installation d’un VS Code server sur l’instance de calcul.

Il ne reste plus qu’à “faire confiance” aux fichiers qui seront maintenant accessible depuis l’éditeur local.

Plusieurs alertes peuvent être levées par VS Code selon votre paramétrage. Ainsi, il faudra choisir un interpréteur Python (mais attention, ce n’est pas une distribution installée sur votre poste local !) ainsi que s’authentifier vis à vis de l’extension Azure pour VS Code (à installer également au préalable).

Si le choix entre Python2 et Python3 ne fait plus débat, le choix de la version mineure de Python est important pour votre projet, car celle-ci conditionne les versions de packages nécessaires par la suite.

Le réflexe pourrait alors être de choisir l’interpréteur Python du poste local mais nous voulons justement exécuter le code à distance. Une sélection dans la fenêtre ci-dessous n’est donc pas nécessaire.

Etape à ne PAS réaliser

Allons plutôt voir dans le coin en bas à droite de notre éditeur.

Un clic sur “Jupyter Server” va lancer un autre menu : “Pick how to connect to Jupyter“.

Nous choisissons ici “Azure ML Compute Instances” puis naviguons jusqu’à l’instance voulue. Un message d’alerte concernant la facturation de cette VM va alors apparaître.

Un rechargement de VS Code pourra être nécessaire pour bien valider la connexion.

Le statut en bas à droite de l’IDE nous indique que le serveur Jupyter se trouve bien à distance (remote) et c’est donc lui qui exécutera le code.

Commençons par un commande simple dans un nouveau notebook.

Il est maintenant temps de choisir l’interpréteur Python de la machine distante (cliquer si besoin sur le bouton “sélectionner le noyau”).

Nous choisissons ici l’environnement Python 3.8, muni des packages azureml, qui nous permettront d’interagir avec le service Azure Machine Learning.

Vérification de la version du package azureml-core

Utiliser les commandes Git de VSC

C’est sans doute le plus grand des avantages à passer par l’IDE : pouvoir réaliser du contrôle de code source, avec les fonctionnalités Git intégrées dans VSC.

En cliquant sur le symbole Git du menu du gauche, nous déroulons une première fenêtre demandant de réaliser l’initialisation du repository.

Il est ensuite nécessaire de configurer le nom de l’utilisation ainsi que son adresse de courrier électronique.

Les changements (ici, tous les fichiers lors de l’initialisation) sont détectés par Git et il sera possible de les pousser (git push) sur le repository.

En cliquant ici sur Push, une alerte sera levée car une dernière configuration est nécessaire.

Il faut configurer l’URL du repository, par exemple une adresse GitHub.

Ceci se réalise au moyen de la commande ci-dessous, sur la branche principale (main).

git push --set-upstream <origin_branch> main

Ca y est ! Nous disposons maintenant d’une configuration très efficace pour travailler nos développements de Machine Learning, en lien avec le service Azure ML. Ce système permet également une véritable isolation des fichiers pour chaque développeur. Aucun risque qu’un.e collègue vienne modifier vos scripts, autrement qu’au travers d’un merge de branches.

Repos Databricks & GitHub actions, better together !

Ou en bon français, comment faire coopérer la nouvelle (mars 2021) fonctionnalité Repos de Databricks avec une logique d’intégration continue développée dans GitHub ?

Revenons tout d’abord sur le point à l’origine de la nécessité d’un tel mécanisme (une présentation plus détaillée des Repos Databricks a été faite et mise à jour sur ce blog). Dans une architecture dite “moderne” sur Azure, nous utilisons Azure Data Factory pour piloter les traitements de données et lancer conjointement des notebooks Databricks. Cela nécessite de donner le chemin du notebook sur l’espace de travail déclaré en tant que service lié.

Nous disposons de trois entrées pour définir le “notebook path“.

Si nous choisissons “Repos”, nous allons donner un chemin contenant le nom du développeur !

Les développements livrés en production doivent bien sûr être totalement indépendants d’une telle information. Un contournement consisterait à utiliser un sous-répertoire Shared dans la partie Repos mais cela reviendrait à perdre le rattachement du développement à son auteur.

Nous allons donc mettre en place un mécanisme permettant aux développeurs d’utiliser leur propre répertoire puis de versionner les développements dans un espace tiers, par exemple un repository GitHub (une démarche exploitant les repositories Azure DevOps serait tout à fait similaire).

Voici le processus suivi lors d’un développement :

  • le développeur A crée une nouvelle branche sur laquelle il réalise ses développements
  • il “commit & push” ensuite son travail
  • une Pull Request (PR) peut alors être soumise dans le but de fusionner le travail avec la branche principale (master ou main)
  • le développeur B vient alors valider la PR et le merge se lance sur la branche principale

C’est alors que se déclenche notre processus d’intégration continue (CI) qui réalise une copie des notebooks de la branche principale dans le répertoire /Shared de l’espace de travail Databricks.

Nous nous assurons ainsi d’avoir toujours la dernière “bonne” version des notebooks, sur un chemin qui sera identique entre les environnements. En effet, dans un second temps, un processus de déploiement continu (CD) viendra copier ces notebooks sur les autre environnements (qualification, production).

Comment réaliser une GitHub Action d’intégration continue

Nous nous plaçons dans le repository servant à versionner les notebooks. Le menu Actions est accessible dans la barre supérieure.

Puis, nous cliquons sur le lien “set up a workflow yourself“.

Un modèle de pipeline YAML est alors disponible et nous allons l’adapter.

Détaillons maintenant le code final, étape par étape.

# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  pull_request:
    branches: [ master ]
    paths: ['**.py']

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest
    env:
      DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }}
      DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - name: Checkout
        uses: actions/checkout@v2
      
      - name: Setup Python
        uses: actions/setup-python@v2.2.2
        with:
          python-version: '3.8.10'
          architecture: 'x64'
      
      # Install pip
      - name: Install pip
        run: |
          python -m pip install --upgrade pip

      # Install databricks CLI with pip
      - name: Install databricks CLI
        run: python -m pip install --upgrade databricks-cli

      # Runs
      - name: Run a multi-line script
        run: |
          echo Test python version
          python --version
          echo Databricks CLI version
          databricks --version
          
      # Import notebook to Shared
      - name: Import local github directory to Shared
        run: databricks workspace import_dir --overwrite . /Shared
          
      # List Shared content
      - name: List workspace with databricks CLI
        run: databricks workspace list --absolute --long --id /Shared

Le principe est d’utiliser une machine virtuelle munie d’un système d’exploitation Ubuntu (‘latest’ pour obtenir la dernière version disponible), d’y installer un environnement Python dans la version souhaitée (ici, 3.8.10) puis le CLI de Databricks.

Pour connecter ce dernier à notre espace de travail, nous définissons au préalable deux variables d’environnement :

  • DATABRICKS_HOST qui contient l’URL de notre espace de travail
  • DATABRICKS_TOKEN qui contient un jeton d’authentification

Pour ne pas faire figurer ces valeurs dans le fichier YAML (qui est lui-même versionné dans le repository), nous définirons au préalable deux secrets à l’aide du menu Settings.

Nous choisissons ici une portée des secrets au niveau repository.

L’instruction uses: actions/checkout@v2 garantit la disponibilité des fichiers archivés dans le repository (ceux-ci sont copiés sur la machine virtuelle) et le point (.) indique simplement ce chemin dans l’instruction du CLI import_dir.

Ainsi, dès la fusion d’une Pull Request avec la branche principale, le code se lance automatiquement et nous pouvons vérifier sa bonne exécution dans le menu “View runs”.

Une telle approche sera bien sûr complétée idéalement par une stratégie de tests portant sur le contenu des notebooks ou mieux encore, sur du code packagé qui y sera utilisé (voir cet article).