Paramètres de ressources du conteneur
- Leçon 11
- 45 min
- Module 4 : Choisir l'infrastructure et mesurer
Leçon 11 sur 15 · Déployer des modèles d'IA pour de vrais utilisateursParamètres de ressources du conteneur
1. Définir le contrat d'inférence
2. Choisir un mode de service
3. Créer le service de prédiction
4. Choisir l'infrastructure et mesurer
5. Mettre en production, surveiller et rétablir
Ce que vous saurez faire
Placez le service d’escalade dans un conteneur et définissez sa taille : quantité de CPU, mémoire et nombre de processus. Voyez ensuite l’effet de chaque paramètre sur le démarrage, les requêtes et la mémoire. Faites-le avant qu’une plateforme infonuagique impose les mêmes limites.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les Dockerfiles, le cache des couches et
.dockerignore, dans Construire efficacement du cours L’essentiel de Docker et du nuage pour les développeurs en IA. Un utilisateur sans privilèges root, dans Sécurité à l’exécution du cours sur Docker. - Compose et
.env, dans Configuration à l’exécution du cours sur Docker.cpusetmem_limitdans la simulation du nuage, dans Configuration opérationnelle du cours sur Docker. - Chaque processus charge sa propre copie de l’ensemble, et une empreinte incorrecte provoque une erreur au démarrage. Voir Charger et initialiser : une copie par processus.
- Le document de décision, dans CPU, GPU et points de terminaison gérés.
Ce qu’il vous faut sur votre ordinateur
- Le projet
~/projects/escalation-service, avec son.venvactif etdocs/infrastructure.md, dans CPU, GPU et points de terminaison gérés. S’il manque ou ne fonctionne plus, copiez l’instantanéend-of-m03, comme dans Utiliser un instantané. - Docker Desktop en cours d’exécution, dans Installer et vérifier du cours sur Docker. La première construction télécharge environ 90 Mo.
Testé avec Python 3.14.6 et Docker Desktop (Docker Engine 29.6.1) sur macOS, avec une puce Apple. Les étapes pour Windows et Linux ne sont pas testées. Les durées dépendent de votre ordinateur.
Un fusible et une feuille de réservation
Section intitulée « Un fusible et une feuille de réservation »Un appartement a un fusible. Si l’appartement utilise trop d’électricité à la fois, le fusible coupe le courant. Tout l’appartement devient sombre, pas seulement une lampe. Une salle de réunion partagée a une feuille de réservation. Supposons que vous pouvez l’utiliser pendant 30 minutes par heure. Après vos 30 minutes, vous attendez dehors jusqu’à l’heure suivante, même si la salle est vide.
Un conteneur reçoit les deux mêmes types de limites. Une limite de mémoire est le fusible : si les processus du conteneur en utilisent davantage, le système d’exploitation les arrête. Une limite de CPU est la feuille de réservation. Le conteneur peut utiliser le CPU pendant une partie de chaque courte période, puis il attend.
Le problème : même image, moitié moins de CPU
Section intitulée « Le problème : même image, moitié moins de CPU »Amira exécute la même image deux fois en changeant un seul paramètre : --cpus 1, puis --cpus 0.5. Le code et le modèle restent identiques. Elle mesure avec le banc d’essai de la prochaine leçon, une requête à la fois :
| Limite de CPU | Prêt après (médiane de 8 démarrages) | p50, une requête à la fois | p95, une requête à la fois |
|---|---|---|---|
| 1 | 2,0 s | 4,1 ms | 6,1 ms |
| 0,5 | 3,5 s | 4,1 ms | 44,1 ms |
Avec moitié moins de CPU, le démarrage prend presque deux fois plus de temps. Amira s’y attendait. La surprise est le p95 : la durée sous laquelle restent 95 % des requêtes (la prochaine leçon l’explique). La plupart des requêtes sont aussi rapides qu’avant. Mais une sur vingt prend dix fois plus de temps, sans autre requête en attente. Le budget de Grace est de 50 ms pour le p95, réseau compris. Dans cette mesure, un demi-CPU utilise presque tout ce budget.
Pour comprendre pourquoi, Amira doit d’abord créer le conteneur et placer ses paramètres dans des fichiers.
Écrire le Dockerfile
Section intitulée « Écrire le Dockerfile »Le Dockerfile vient du cours sur Docker, dans Sécurité à l’exécution. Il utilise les noms de ce projet et une nouvelle ligne : COPY bundles ./bundles.
FROM python:3.14.8-slim-trixie
ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ PIP_ROOT_USER_ACTION=ignore
RUN useradd --create-home --uid 10001 app
WORKDIR /app
COPY requirements-run.txt .RUN pip install -r requirements-run.txt
COPY bundles ./bundlesCOPY pyproject.toml .COPY escalation ./escalation
USER app
EXPOSE 8000CMD ["fastapi", "run", "--host", "0.0.0.0", "--port", "8000"]COPY bundles ./bundles place l’ensemble du modèle dans l’image. On appelle cela intégrer l’ensemble à l’image. L’autre méthode consiste à conserver l’ensemble dans un espace de stockage, par exemple un compte de stockage infonuagique. On le télécharge ensuite au démarrage du conteneur.
Tomás : « Pourquoi l’intégrer? Un nouveau modèle a alors besoin d’une nouvelle image. » Oui, et c’est justement la raison. L’image escalation-service:1.0.0 contient alors le code et le modèle que l’équipe a testés ensemble. Chaque version de l’image est complète. Pour revenir en arrière, on peut donc exécuter l’ancienne image, avec l’ancien code et l’ancien modèle. Dans le module 5, l’image contient plusieurs ensembles. Retour en arrière et retrait permet aussi de rétablir seulement le modèle, avec un paramètre dans .env. L’image démarre sans appel réseau au stockage, ce qui enlève une étape au démarrage à froid. Le coût : chaque nouveau modèle exige une construction, et l’image grossit avec le modèle. Pour un ensemble de 45 Ko, ce coût est négligeable. Pour un modèle de langage de 5 Go, les équipes chargent le fichier depuis le stockage au démarrage. Elles vérifient quand même son empreinte, comme dans Confiance et intégrité des artefacts.
La ligne vient après pip install et avant le code, à cause du cache des couches. Voir Construire efficacement : le cache des couches dans le cours sur Docker. Les paquets changent le moins, le code change le plus, et l’ensemble se situe entre les deux. MODEL_SHA256 n’est pas dans l’image. Il vient de l’extérieur au démarrage du conteneur, pour que l’image ne puisse pas confirmer son propre ensemble.
Ajoutez cette ligne à La décision dans docs/infrastructure.md :
- The bundle is baked into the image: each image holds the code and the model that were tested together.Créer requirements-run.txt et .dockerignore
Section intitulée « Créer requirements-run.txt et .dockerignore »requirements-run.txt liste chaque paquet de l’image avec sa version exacte. Créez-le dans un conteneur Linux, comme dans L’essentiel du Dockerfile du cours sur Docker, car l’image utilise Linux :
docker run --rm python:3.14.8-slim-trixie sh -c 'pip install -q --root-user-action=ignore --disable-pip-version-check "fastapi[standard-no-fastapi-cloud-cli]==0.142.2" scikit-learn==1.9.1 pandas==3.0.6 numpy==2.5.3 joblib==1.6.0 && pip freeze' > requirements-run.txtCela a pris 18 s. wc -l requirements-run.txt donne 60 requirements-run.txt. Les cinq paquets fixent les versions que l’ensemble vérifie. Les 55 autres les accompagnent, par exemple SciPy avec scikit-learn.
Ensuite, créez .dockerignore comme liste d’autorisation, comme dans le cours sur Docker. La nouvelle ligne concerne les ensembles :
# Send only what the image needs. Data, tests, .env and .venv stay out.*!requirements-run.txt!pyproject.toml!escalation/!bundles/**/__pycache__/Construire l’image
Section intitulée « Construire l’image »docker build -t escalation-service:1.0.0 .La sortie (abrégée; une construction sans cache) :
#4 [internal] load build context#4 transferring context: 46.61kB done...#9 [5/8] RUN pip install -r requirements-run.txt#9 DONE 19.1s#10 [6/8] COPY bundles ./bundles#10 DONE 0.1s...#13 naming to docker.io/library/escalation-service:1.0.0 doneLa construction a pris 31 s sur l’ordinateur du cours. Regardez maintenant l’image et ses couches :
docker image ls escalation-servicedocker history escalation-service:1.0.0 --format "{{.Size}}\t{{.CreatedBy}}"La sortie (abrégée) :
IMAGE ID DISK USAGE CONTENT SIZE EXTRAescalation-service:1.0.0 1ca084f354f7 787MB 166MB...69.6kB COPY escalation ./escalation # buildkit12.3kB COPY pyproject.toml . # buildkit45.1kB COPY bundles ./bundles # buildkit461MB RUN /bin/sh -c pip install -r requirements-r…...110MB # debian.sh --arch 'arm64' out/ 'trixie' '@1…Description textuelle du graphique
Un graphique à colonnes des couches de escalation-service:1.0.0, en Mo. L’image de base (Debian et Python) : 160 Mo. RUN pip install : 461 Mo. COPY bundles, l’ensemble du modèle : 45,1 Ko, trop petit pour apparaître comme colonne. COPY escalation, le code : 69,6 Ko, aussi trop petit pour apparaître.
Le modèle fait 5 Ko, et les bibliothèques qui l’exécutent font 461 Mo : SciPy seul fait environ 149 Mo. L’image qu’une plateforme télécharge correspond à la taille du contenu, soit 166 Mo compressés. Le ticket-api du cours sur Docker faisait 73 Mo. Une image plus grosse prend plus de temps à télécharger à chaque démarrage à froid dans le nuage.
Compose : la taille dans un seul fichier
Section intitulée « Compose : la taille dans un seul fichier »La taille du conteneur va dans compose.yaml, pour que chaque démarrage utilise les mêmes paramètres :
# The escalation service on this computer, with the resource limits of a# small cloud container. MODEL_SHA256 comes from .env (copy .env.example).services: escalation: image: escalation-service:1.0.0 build: . ports: - "127.0.0.1:8000:8000" environment: MODEL_BUNDLE: bundles/escalation-${MODEL_VERSION:-1.0.0} MODEL_SHA256: ${MODEL_SHA256:?Set MODEL_SHA256 in .env} SHOW_DOCS: "true" command: ["fastapi", "run", "--host", "0.0.0.0", "--port", "8000", "--workers", "${WORKERS:-1}"] cpus: ${CPUS:-1.0} mem_limit: ${MEMORY:-512m} memswap_limit: ${MEMORY:-512m} # the same value: no swap, as in most cloud containers healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/ready')"] interval: 5s timeout: 3s retries: 3 start_period: 10scpusest la limite de CPU :1.0correspond à un CPU,0.5à un demi-CPU.mem_limitest la limite de mémoire.memswap_limitavec la même valeur signifie aucune mémoire d’échange. La mémoire d’échange est un espace disque que le système utilise comme mémoire supplémentaire, mais lente. Docker l’autorise par défaut, et la plupart des conteneurs infonuagiques n’en ont pas. Le portable doit donc échouer comme dans le nuage.--workersdéfinit le nombre de processus (workers), les processus du serveur qui répondent aux requêtes. Chacun charge sa propre copie de l’ensemble, comme dans Charger et initialiser.- Le contrôle de santé appelle
/ready, pour que Compose sache quand l’ensemble est prêt.
${WORKERS:-1} lit une variable de .env, avec 1 comme valeur par défaut. ${MODEL_SHA256:?…} n’a pas de valeur par défaut : sans cette variable, Compose s’arrête avec le message après ?. Les valeurs vont dans .env, que Git ignore. .env.example est le modèle que Git conserve :
# Copy this file to .env and fill in MODEL_SHA256.# MODEL_VERSION picks the bundle: bundles/escalation-<MODEL_VERSION>.MODEL_VERSION=1.0.0# The digest that make_bundle.py printed for that bundle (64 characters).MODEL_SHA256=# The container's limits (C17 Module 4).WORKERS=1CPUS=1.0MEMORY=512mAmira le copie dans .env et démarre Compose avant d’ajouter l’empreinte :
cp .env.example .envdocker compose up -d --waitLa sortie :
error while interpolating services.escalation.environment.MODEL_SHA256: required variable MODEL_SHA256 is missing a value: Set MODEL_SHA256 in .envBien : pas d’empreinte, pas de démarrage. Elle colle l’empreinte de son ensemble après MODEL_SHA256= et redémarre. Utilisez shasum -a 256 bundles/escalation-1.0.0/SHA256SUMS, comme dans le module 1; votre empreinte diffère de celle du cours :
docker compose up -d --waitdocker stats --no-streamLa sortie (abrégée) :
Container escalation-service-escalation-1 Started Container escalation-service-escalation-1 Waiting Container escalation-service-escalation-1 HealthyCONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % ...eeeea9b9ac05 escalation-service-escalation-1 0.41% 148.7MiB / 512MiB 29.05% ...Un processus utilise environ 149 Mio dans le conteneur, sur une limite de 512 Mio.
Vérifier la parité dans le conteneur
Section intitulée « Vérifier la parité dans le conteneur »Dans Vérifier la parité, vous avez vérifié le service sur votre ordinateur. Maintenant, les mêmes billets de soutien vont au conteneur, avec Linux, les paquets de l’image et 1 CPU. Chaque nouvel environnement peut apporter ses propres différences. Refaites donc la vérification :
python parity/check_parity.py http://127.0.0.1:8000 bundles/escalation-1.0.0La sortie (abrégée) :
case ticket expected served differencenormal T-116940 0.187598 0.187598 2.8e-17normal T-116941 0.064735 0.064735 0.0e+00normal T-116942 0.129584 0.129584 1.1e-16...missing T-900001 0.037929 0.037929 9.7e-17...14 of 14 cases match within 1e-09.Les différences atteignent 1,1 × 10⁻¹⁶, un peu plus que sur macOS, mais bien en dessous de la tolérance. Le code de sortie est 0. Exécutez cette vérification après chaque nouvelle image et chaque déploiement.
Limites de CPU et bridage
Section intitulée « Limites de CPU et bridage »Comment Docker donne-t-il un demi-CPU à un conteneur? Il ne ralentit pas le CPU. Il compte le temps par courtes périodes de 100 ms. Avec cpus: 0.5, le conteneur peut utiliser 50 ms de temps CPU par période. Après ces 50 ms, le système le met en pause jusqu’au début de la période suivante. Cette pause s’appelle le bridage.
Description textuelle du diagramme
Deux lignes du temps de 300 ms, avec une ligne pointillée au début de chaque période de 100 ms. Avec cpus: 1.0, le conteneur occupé s’exécute sans arrêt. Avec cpus: 0.5, il s’exécute pendant 50 ms, puis reste en pause pendant 50 ms, à chaque période.
En dessous, une requête arrive à 60 ms dans le conteneur à 0,5 CPU. Les 50 ms de cette période sont déjà utilisées. La requête attend donc 40 ms jusqu’au début de la période suivante. Elle demande ensuite environ 3 ms de travail. Elle prend environ 43 ms plutôt que 3 ms.
Cela explique le p95 de 44,1 ms. Le banc d’essai envoie la requête suivante dès que la dernière réponse arrive. Le service est donc occupé presque tout le temps. Il utilise ses 50 ms tôt dans de nombreuses périodes. La plupart des requêtes se terminent encore avant la pause : le p50 reste à 4,1 ms. Certaines arrivent pendant une pause et doivent attendre : le p95 augmente brusquement. Dans le nuage, la taille choisie (0,25, 0,5 ou 1 vCPU) est une limite de CPU de ce type. Le bridage exige toutefois un conteneur occupé. Un service qui répond occasionnellement à une requête n’épuise jamais ses 50 ms. La prochaine leçon examine si le service de Larkfield est parfois aussi occupé.
Démarrage à froid et CPU
Section intitulée « Démarrage à froid et CPU »La limite de CPU change aussi le démarrage. Le démarrage demande du travail au CPU : importer les bibliothèques et charger l’ensemble, dans Charger et initialiser. Amira a démarré l’image huit fois avec chaque limite, alors que l’image était déjà sur l’ordinateur. Elle a mesuré le temps entre docker run et la réponse de /ready :
Description textuelle du graphique
Un graphique à colonnes du temps médian entre docker run et la première réponse de /ready, pour huit démarrages chacun. 1 CPU : 2,0 s (de 1,6 à 3,0 s). 0,5 CPU : 3,5 s (de 2,7 à 5,6 s). 0,25 CPU : 6,7 s (de 5,9 à 11,1 s).
Moitié moins de CPU, environ deux fois plus de temps : le démarrage dépend presque entièrement du travail du CPU. La dispersion (de 1,6 à 3,0 s avec 1 CPU) vient des autres programmes sur le même portable. Dans le nuage, la plateforme doit aussi trouver une machine et télécharger l’image de 166 Mo. L’équipe du cours l’a mesuré sur Azure Container Apps, en conditions réelles. La première requête après un passage à zéro réplique a pris 26,4 s avec 0,5 vCPU et 23,9 s avec 1 vCPU. Doubler le CPU n’a économisé que 2,5 s. Dans le nuage, la majeure partie du démarrage à froid dépend de la plateforme, pas du CPU.
Processus et concurrence
Section intitulée « Processus et concurrence »La concurrence est le nombre de requêtes en cours au même moment. Un processus exécute Python, et Python dans un seul processus utilise environ un CPU pour ce travail. Un processus répond donc à environ une requête à la fois, et les autres attendent dans une file.
Davantage de processus peuvent répondre à davantage de requêtes en même temps, mais seulement s’ils ont assez de CPU. Chacun consomme aussi de la mémoire. Avec deux processus, docker top montre les processus dans le conteneur :
PID RSS COMMAND32317 58944 /usr/local/bin/python3.14 /usr/local/bin/fastapi run --host 0.0.0.0 --port 8000 --workers 232351 17412 /usr/local/bin/python3.14 -B -c from multiprocessing.resource_tracker import main;main(6)32352 198664 /usr/local/bin/python3.14 -B -c from multiprocessing.spawn import spawn_main; ...32353 197448 /usr/local/bin/python3.14 -B -c from multiprocessing.spawn import spawn_main; ...RSS est la mémoire de chaque processus, en Kio (1 024 octets). Un processus parent d’environ 58 Mio surveille les processus de service, et chacun des deux utilise environ 194 Mio. Chaque processus a son propre Python, ses propres bibliothèques et son propre ensemble. Deux processus ne partagent pas une seule copie.
Pourquoi docker stats montre-t-il 149 Mio pour tout le conteneur avec un processus, alors qu’un processus utilise ici 194 Mio? Les deux outils comptent différemment. RSS compte chaque page mémoire qu’un processus utilise. Cela inclut les pages de fichiers de bibliothèques que le système peut partager ou supprimer. docker stats compte la mémoire du conteneur une seule fois, sans le cache de fichiers que le système peut libérer. La limite de mémoire compte comme docker stats; comparez donc la limite avec docker stats. Pour planifier, prévoyez environ 190 Mio par processus. C’est un peu plus que ce que chaque processus supplémentaire ajoute réellement, ce qui laisse une marge.
Amira a mesuré avec plus de processus que de CPU : une limite de 2 CPU, 16 requêtes simultanées, 2 000 requêtes.
| Processus (2 CPU) | Requêtes par seconde | p50 | Mémoire |
|---|---|---|---|
| 2 | 524 | 28,0 ms | 350 Mio |
| 4 | 317 | 49,3 ms | 631 Mio |
| 6 | 262 | 76,4 ms | 914 Mio |
Quatre processus sur deux CPU sont plus lents que deux, et utilisent presque deux fois plus de mémoire. Les processus supplémentaires n’ajoutent pas de temps CPU. Ils utilisent les deux mêmes CPU à tour de rôle, et ces changements prennent du temps. Pour un service limité par le CPU comme celui-ci, la règle est : processus = CPU. La mémoire doit être ≥ nombre de processus × environ 200 Mio, plus une marge.
Exercice guidé : dimensionner le conteneur
Section intitulée « Exercice guidé : dimensionner le conteneur »- Dans
~/projects/escalation-service, écrivez les fichiersDockerfile,.dockerignore,compose.yamlet.env.examplede cette leçon. - Créez
requirements-run.txtdans un conteneur. Vérifiez qu’il contient 60 lignes. - Ajoutez la ligne sur l’ensemble à La décision dans
docs/infrastructure.md. - Construisez l’image avec
docker build -t escalation-service:1.0.0 .. - Exécutez
docker history escalation-service:1.0.0. Trouvez la coucheCOPY bundleset la couchepip install. - Copiez
.env.exampledans.env, puis collez l’empreinte de votre ensemble aprèsMODEL_SHA256=. - Démarrez avec
docker compose up -d --wait. Résultat attendu :Healthy(en bonne santé). - Exécutez
docker stats --no-stream. Notez la mémoire. - Appelez
/ready:curl -s http://127.0.0.1:8000/ready. Résultat attendu :{"status":"ready","model_version":"1.0.0"}. - Exécutez
python parity/check_parity.py http://127.0.0.1:8000 bundles/escalation-1.0.0. Résultat attendu :14 of 14 cases match within 1e-09.(les 14 cas sur 14 concordent avec une tolérance de 1 × 10⁻⁹).
Afficher la sortie attendue des étapes 5 et 8
Étape 5 : COPY bundles ./bundles fait environ 45 Ko, et RUN pip install environ 461 Mo sur une puce Apple. Les tailles sur un ordinateur Intel ou AMD (amd64) sont un peu différentes.
Étape 8 : environ 150 Mio sur 512 Mio, par exemple 148.7MiB / 512MiB. Si la mémoire est environ deux fois plus élevée, vérifiez WORKERS dans .env : sa valeur doit être 1.
À vous de jouer : deux processus sur un CPU
Section intitulée « À vous de jouer : deux processus sur un CPU »Dans .env, définissez WORKERS=2 et gardez CPUS=1.0. Avant de commencer, faites deux prédictions : la mémoire dans docker stats et le nombre de requêtes par seconde. Deux processus répondront-ils à plus de requêtes qu’un seul? Ensuite, exécutez docker compose up -d --wait et docker stats --no-stream.
Afficher la réponse
La mémoire double, environ : 337.5MiB / 512MiB, car chaque processus charge ses propres bibliothèques et son propre paquet. La vitesse n’augmente pas : deux processus partagent un CPU, donc ils travaillent à tour de rôle. La prochaine leçon le mesure : avec 8 requêtes simultanées, un processus répondait à 216 requêtes par seconde. Deux processus sur un CPU répondaient à 209 requêtes par seconde. Vous utilisez deux fois plus de mémoire pour rien. Rétablissez WORKERS=1.
Erreur courante
En santé, puis arrêté : quatre processus dans 512 Mio
Symptôme : Tomás veut plus de vitesse et définit WORKERS=4, avec CPUS=2.0 et MEMORY=512m. docker compose up -d --wait indique :
Container escalation-service-escalation-1 Healthyet /ready répond 200. Dix secondes plus tard, le service ne répond plus du tout.
Cause : quatre processus ont besoin de plus de 512 Mio. Chaque processus charge ses bibliothèques et son paquet. Le système arrête des processus par manque de mémoire quand le conteneur dépasse sa limite. Le journal montre les processus qui s’arrêtent, tandis que le processus parent en démarre de nouveaux :
INFO: Waiting for child process [89]INFO: Child process [89] diedINFO: Waiting for child process [90]INFO: Child process [90] died...INFO escalation: model 1.0.0 loaded (bundle 8f233029aa4c...) in 2501 msLa vérification de santé a réussi, car un processus a répondu à /ready pendant que les autres s’arrêtaient. Ensuite, le système a aussi arrêté le processus principal. docker inspect donne la raison :
docker inspect escalation-service-escalation-1 --format "{{.State.Status}} OOMKilled={{.State.OOMKilled}} exit={{.State.ExitCode}}"exited OOMKilled=true exit=137Le code de sortie 137 signifie que quelque chose a arrêté le processus. OOMKilled=true indique pourquoi : manque de mémoire.
Description textuelle du diagramme
Barres horizontales de la mémoire du conteneur après le démarrage, avec une ligne pointillée à la limite de 512 Mio. Aucune mémoire d’échange. 1 processus : 148,7 Mio. 2 processus : 340,3 Mio. 3 processus : 481,5 Mio, près de la ligne. 4 processus : plus de 512 Mio, donc le noyau a arrêté le conteneur : OOMKilled, code de sortie 137.
Solution : mesurez la mémoire d’un processus, multipliez-la par le nombre de processus et ajoutez une marge. Avec une limite de 2 Gio, quatre processus utilisaient 631 Mio (le tableau dans Processus et concurrence). Donnez-leur donc au moins 1 Gio. Ou utilisez moins de processus : sur 2 CPU, 2 processus sont de toute façon plus rapides. Vérifiez OOMKilled (arrêt par manque de mémoire) chaque fois qu’un conteneur s’arrête sans raison claire.
Pour un vrai service d’IA : avec la mémoire d’échange autorisée (le réglage par défaut de Docker), les quatre mêmes processus restaient running (en cours d’exécution) à 511.9MiB / 512MiB. L’ordinateur portable cachait le problème, et seul le nuage le révélerait. C’est pourquoi compose.yaml définit memswap_limit. Un grand modèle aggrave le problème : un modèle de 2 Go dans chacun des quatre processus représente 8 Go. Des vérifications de santé réussies au démarrage ne prouvent pas que le service survit à sa première minute.
Vérifiez votre compréhension
Avec cpus : 0.5, p50 reste à 4,1 ms, mais p95 passe de 6,1 à 44,1 ms. Expliquez pourquoi seules les requêtes lentes changent.
Afficher la réponse
Docker accorde au conteneur 50 ms de temps CPU dans chaque période de 100 ms. Un service occupé utilise ses 50 ms rapidement, puis le système le suspend jusqu’à la période suivante : c’est le bridage. Les requêtes exécutées avant la pause prennent les 4 ms habituelles, donc la médiane ne change pas. Les requêtes qui arrivent pendant une pause attendent la période suivante, jusqu’à environ 50 ms. Elles représentent les quelques pour cent les plus lentes, donc p95 augmente fortement.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Faites une prédiction : dans un conteneur avec 1 CPU et 1 Gio, Tomás définit WORKERS=4. Qu'arrive-t-il à la mémoire et aux requêtes par seconde, comparativement à un seul processus?
Afficher la réponse
La mémoire augmente beaucoup. Amira a mesuré 631 Mio pour quatre processus et environ 150 Mio pour un seul. Chaque processus charge ses propres bibliothèques et son propre paquet. Le tout tient encore dans 1 Gio, donc rien n’est arrêté. Le nombre de requêtes par seconde n’augmente pas. Quatre processus travaillent à tour de rôle sur un CPU, et les changements de processus prennent un peu de temps. Le service peut donc ralentir. Les processus supplémentaires aident seulement quand des CPU sont disponibles pour eux.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Pourquoi le cours intègre-t-il le paquet à l'image? Quand chargeriez-vous plutôt le modèle depuis le stockage au démarrage?
Afficher la réponse
Avec le paquet intégré, l’image contient le code et le modèle que l’équipe a testés ensemble. L’image est complète, et exécuter une ancienne image rétablit les deux. (Le module 5 permet aussi de rétablir seulement le modèle, avec un paramètre.) Le démarrage ne nécessite aucun téléchargement depuis le stockage. Chargez le modèle depuis le stockage lorsqu’il est volumineux (plusieurs gigaoctets). Ainsi, un nouveau modèle ne nécessite pas de nouvelle image, et l’image reste petite. Vérifiez alors l’empreinte du fichier téléchargé au démarrage, comme le fait load_bundle.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Le
Dockerfileintègre le paquet à l’image : chaque image contient le code et le modèle testés ensemble. Le modèle occupe 45,1 ko; les bibliothèques occupent 461 Mo. compose.yamldéfinit les ressources :cpus,mem_limit,memswap_limit(sans mémoire d’échange) et--workers, avec les valeurs de.env.- Une limite de CPU suspend un conteneur occupé dans chaque période de 100 ms (bridage). À 0,5 CPU, p95 était de 44,1 ms, et le démarrage à froid prenait environ deux fois plus de temps.
- Chaque processus possède sa propre copie de tout. Processus = CPU; mémoire ≥ processus × environ 200 Mio.
- Trop de processus pour la mémoire :
Healthy(en santé), puisOOMKilled=true, code de sortie 137.
Ensuite, vous mesurez le service de la même façon chaque fois : une charge fixe, p50 et p95, débit et concurrence.
Leçon suivante : Mesurer la performance du service