Zum Inhalt springen

Konfiguration und Routen des Modelltrainings

Jeder Schlüssel des Blocks model_training, wie Läufe eingereiht, bewertet und gespeichert werden, und die Admin- und Agent-Routen, dazu Lösungen für häufige Laufzeitfehler.

Neu in v2.14.0

Diese Seite ist die Referenz für den Block model_training einer Potato-Konfiguration und dafür, was mit einem Lauf geschieht, nachdem du ihn gestartet hast. Die Seite richtet sich an Personen, die eine Studie betreiben und Läufe begrenzen, Läufe an eine andere Maschine schicken, die Vorhersagen eines Modells Annotatoren zeigen oder das Training aus einem Skript starten wollen. Jeder Schlüssel, jeder Standardwert, jede Route und jede Meldung unten wurde am Code von Potato geprüft. Für einen ersten Lauf beginne mit Dein erstes Modell trainieren. Die Trainer und ihre Parameter sind in Trainer aufgelistet.

Eine vollständige Konfiguration

Die folgende Konfiguration trainiert einen Sentiment-Klassifikator auf Produktrezensionen. auto_retrain passt sklearn-text nach je 25 neuen Annotationen neu an. Mit den standardmäßigen TF-IDF-Features trainiert sklearn-text auf der CPU und braucht nichts außer einer einfachen Installation von Potato. Häufige automatische Läufe kosten deshalb wenig. Das Rückschreiben zeigt Annotatoren jede Vorhersage mit einer Konfidenz von mindestens 0,8. Mit include_unlabeled sagt ein Lauf auch an den Rezensionen voraus, die noch niemand gelabelt hat. Die Standardwerte von hf-text-classification und das Backend gpu-server bedienen Läufe, die von Hand auf der Training-Seite gestartet werden:

yaml
annotation_task_name: "Review sentiment with model training"
task_dir: "."
data_files:
  - "data/reviews.json"
item_properties:
  id_key: "id"
  text_key: "text"
output_annotation_dir: "annotation_output"
user_config:
  allow_all_users: true
 
annotation_schemes:
  - annotation_type: "radio"
    name: "sentiment"
    description: "Is this review positive or negative?"
    labels: ["positive", "negative"]
 
model_training:
  enabled: true
  split_seed: 0
  splits: {train: 0.7, val: 0.15, test: 0.15}
  aggregation: consensus
  retain_runs: 5
  queue_max: 10
  max_wall_seconds: 3600
  max_ram_mb: 8192
  num_threads: 4
  trainer_params:
    sklearn-text:
      classifier: sklearn.linear_model.LogisticRegression
      classifier_kwargs: {max_iter: 1000}
    hf-text-classification:
      model_name: answerdotai/ModernBERT-base
      epochs: 4
      lora: true
  auto_retrain:
    enabled: true
    trainer: sklearn-text
    schemas: [sentiment]
    min_interval_s: 600
    update_frequency: 25
  writeback:
    schemas: [sentiment]
    min_confidence: 0.8
    include_unlabeled: true
  backends:
    gpu-server:
      type: ssh
      host: gpu-server
      python: /opt/conda/bin/python3
      gpus: auto
      min_free_gpu_mb: 4000

Halte split_seed für die gesamte Lebensdauer der Studie fest. Ein neuer Seed weist jedes Item neu zu, sodass zurückgehaltene Items im Training landen und spätere Scores nicht mehr mit früheren vergleichbar sind. Die Konfidenzschwelle min_confidence von 0,8 ist ein Ausgangspunkt, und die richtige Schwelle hängt vom ersten Holdout-Score ab. Schalte include_unlabeled erst ein, wenn dieser Score das Anzeigen von Vorlabels trägt, denn Vorlabels verankern Annotatoren bei der Antwort des Modells.

Schlüssel in model_training

Jeder Schlüssel steht unter model_training:

SchlüsselStandardBedeutung
enabledfalseRegistriert die Training-Seite, die Routen unter /admin/training und die Agent-Routen unter /api/training. Ohne enabled: true gibt es die Routen nicht.
retain_runs5Abgeschlossene Läufe, die im Lauf-Ledger bleiben. Ein älterer Lauf verlässt das Ledger, sofern seine Vorhersagen nicht mehr gespeichert sind. Lauf-Verzeichnisse bleiben auf der Platte.
grace_seconds20Sekunden, die ein abgebrochener Lauf zum Beenden bekommt, bevor Potato den Prozess beendet. 0 oder mehr, und mit dem Auftrag an Backends vom Typ ssh, slurm und agent gesendet.
queue_max10Läufe, die hinter dem laufenden Lauf warten dürfen.
split_seed0Seed für die Zuweisung der Items zu den Splits train, val und test.
splits{train: 0.7, val: 0.15, test: 0.15}Anteile der Splits, so normiert, dass sie sich zu 1 summieren. Die Namen sind train, val und test, und train ist erforderlich.
aggregationconsensusWie die Labels mehrerer Annotatoren zu einem Item zu einem Label werden. consensus passt das Dawid-Skene-Modell an, wenn die Studie mehr als einen Annotator hat, und fällt auf Mehrheitsentscheid zurück, wenn die Anpassung fehlschlägt. majority nimmt den Mehrheitsentscheid.
max_wall_secondsnicht gesetztZeitlimit für einen Lauf. Potato prüft einen lokalen Lauf bei jedem gemeldeten Ereignis und einen ssh- oder Agent-Lauf jede Sekunde.
max_ram_mbnicht gesetztLimit des Adressraums für den Trainingsprozess, gesetzt mit setrlimit. Auf einer Plattform ohne dieses Limit schreibt der Lauf eine Warnung ins Log und läuft weiter.
deviceautoauto, cpu, cuda, cuda:N oder mps. auto nimmt CUDA, dann Apples Metal, dann die CPU.
num_threadsnicht gesetztSetzt OMP_NUM_THREADS, MKL_NUM_THREADS und OPENBLAS_NUM_THREADS für den Lauf, sofern die Umgebung diese Variablen nicht schon setzt.
auto_retrainausErneutes Training, während Annotationen eintreffen. Siehe auto_retrain.
writebackkeinerVeröffentlichung von Vorhersagen als Vorlabels. Siehe writeback.
backendlocalDas Backend für Läufe, die kein anderes nennen.
backendskeineBenannte Backends vom Typ ssh, slurm und agent. Siehe Backends.
max_predictions_mb512Größte predictions.jsonl, die ein Remote-Agent hochladen darf.
trainer_paramskeineStandardwerte für Parameter, nach Trainername geordnet.
tilingenabled: autoZerlegen großer Bilder in Kacheln für die Detektions- und Segmentierungstrainer. Siehe tiling.

Die eigenen params eines Laufs überschreiben trainer_params, und diese überschreiben die Standardwerte des Trainers. Ein Parametername, den der Trainer nicht kennt, wird beim Serverstart abgewiesen, wenn der Name unter trainer_params steht, und sonst beim Start des Laufs. Trainer zeigt die Reihenfolge an einem Beispiel.

auto_retrain

Das automatische erneute Training prüft nach jedem Speichern einer Annotation, ob ein neuer Lauf fällig ist:

SchlüsselStandardBedeutung
enabledfalseSchaltet die Prüfung ein.
trainerkeinerTrainername. Erforderlich.
schemaskeineZu trainierende Schemas. Erforderlich.
min_interval_s600Sekunden seit dem Ende des letzten Laufs, bevor ein weiterer Lauf startet.
update_frequency25Neue Annotationen seit dem letzten automatischen Lauf, bevor ein weiterer Lauf startet.
params{}Trainerparameter, wie die params eines API-Laufs.
backendmodel_training.backendBackend für automatische Läufe.
accept_licencefalseBestätigt die Lizenz eines Trainers, der eine Bestätigung verlangt (ultralytics-detect, ultralytics-segment, layoutlmv3). Ohne die Bestätigung werden automatische Läufe dieser Trainer übersprungen.

Entscheidet eine Prüfung gegen einen Lauf, erscheint der Grund im Feld skip_reason von GET /admin/training/api/status, z. B. „waiting for 3 more annotation(s)“ oder „a run is already in progress“. Automatische Läufe treten nie in die Lauf-Warteschlange ein. Die nächste Annotation, die nach dem Leeren der Warteschlange gespeichert wird, löst die Prüfung erneut aus, mit mehr Annotationen zum Trainieren. Potato hält die Annotationszahlen im Arbeitsspeicher, sodass ein Projekt, das schon einmal trainiert hat, nach einem Neustart neue Annotationen ab dem Neustart zählt.

writeback

Das Rückschreiben entscheidet, welche Vorhersagen Annotatoren sehen:

SchlüsselStandardBedeutung
schemas[]Schemas, deren Vorhersagen Annotatoren sehen. Eine leere Liste zeigt keine.
min_confidence0.0Vorhersagen unter dieser Schwelle werden gespeichert und nicht angezeigt. Eine Vorhersage ohne Konfidenz passiert.
fieldpredictionsItem-Feld, in das die Vorlabels geschrieben werden.
include_unlabeledfalseSagt auch an Items voraus, die noch niemand gelabelt hat, und versieht diese Items mit Vorlabels. Ohne include_unlabeled sagt ein Lauf nur an den eigenen val- und test-Items voraus.

tiling

Die Detektions- und Segmentierungstrainer skalieren jedes Bild auf einige hundert Pixel. Bei dieser Größe gehen die Objekte auf einem Whole-Slide-Bild oder einer Satellitenszene verloren. Für ein image_annotation-, microscopy- oder geo_annotation-Schema zerlegt Potato deshalb jedes große Bild in Kacheln, wenn die Trainingsdaten des Laufs aufgebaut werden. Der Split eines Bildes wird festgelegt, bevor das Bild zerlegt wird, sodass Kacheln eines Slides nie in zwei Splits landen. Vorhersagen werden auf das ganze Bild zurückabgebildet, und Holdout-Scores werden an ganzen Bildern berechnet. Das Vision-Tutorial behandelt Slides und Karten in der Praxis.

SchlüsselStandardBedeutung
enabledautoauto zerlegt Slide-Formate (.svs, .ndpi, .mrxs, .scn und andere) und Bilder, deren längere Seite mindestens auto_min_side misst. true zerlegt jedes Bild und false keines.
auto_min_side4096Länge der längeren Seite in Pixeln, ab der auto ein Bild zu zerlegen beginnt.
tile_size1024Seitenlänge einer Kachel, in den Pixeln, die der Trainer sieht.
overlap128Pixel, die benachbarte Kacheln gemeinsam haben, höchstens die Hälfte von tile_size. Halte die Überlappung mindestens so groß wie die Objekte, sonst wird ein großes Objekt in Stücken gefunden.
downsample1Pixel in voller Auflösung pro Kachelpixel.
mppkeinerMikrometer pro Kachelpixel. Überschreibt downsample bei einem Slide, das seine Auflösung angibt.
background0.25Leere Kacheln, die pro Kachel mit einem Objekt gezogen werden. Setze background: 0 für Slides, die nur teilweise annotiert sind.
max_predict_tiles2000Höchste Zahl an Kacheln, die pro Bild vorhergesagt werden. Die Kacheln mit dem meisten Gewebe bleiben erhalten.
min_visible0.4Anteil eines Objekts, der in einer Kachel liegen muss, damit das Objekt dort trainiert wird.
tissue_threshold0.05Anteil einer Slide-Kachel, der Gewebe sein muss, damit das Modell auf der Kachel vorhersagt.

Slides werden mit openslide gelesen, das pip install "potato-annotation[wsi]" installiert, und zwar auf der Maschine, die die Trainingsdaten aufbaut. Ein Remote-Backend lädt die Kacheln herunter und nie das Slide.

Die Lauf-Warteschlange

Es wird immer nur ein Lauf ausgeführt. Ein Lauf, der von der Training-Seite oder über die API gestartet wird, während ein anderer Lauf läuft, wartet, statt abgewiesen zu werden. Die Startanfrage liefert 202 mit queued: true und der position des Laufs, und die Runs-Tabelle listet den Lauf als waiting #N mit einer Schaltfläche Cancel run. Wenn der laufende Lauf endet, startet der älteste wartende Lauf.

Wartende Läufe sind Einträge im Lauf-Ledger, sodass die Warteschlange einen Neustart überlebt, und der erste wartende Lauf startet, wenn der Server zurückkommt. Ein Lauf, den der vorige Server gerade ausführte, wird zur selben Zeit als interrupted markiert. Die Trainingsdaten eines wartenden Laufs werden erst aufgebaut, wenn der Lauf startet, sodass der Lauf auf den Annotationen trainiert, die in diesem Moment vorliegen. Ein Start, der abgewiesen würde, etwa mit einem unbekannten Schema, wird sofort abgewiesen und nicht erst, wenn der Lauf an der Reihe ist. Ein Lauf, dessen Schema oder Trainer während des Wartens aus der Konfiguration verschwand, schlägt mit invalid_when_dequeued fehl, und der nächste Lauf startet.

Sende "queue": false im Anfragekörper, um 409 statt eines Platzes in der Warteschlange zu erhalten. Warten bereits queue_max Läufe, erhält ein neuer Start 429. GET /admin/training/api/status listet die wartenden Läufe der Reihe nach unter queue.

Prüfungen vor einem Lauf

potato validate meldet jedes Problem im Block model_training auf einmal, und der Server verweigert bei denselben Problemen den Start:

bash
potato validate config.yaml --strict

Die Prüfungen decken diese Fälle ab:

  • splits mit einem anderen Namen als train, val und test, einem Anteil unter 0 oder keiner Zahl, Anteilen, die sich zu 0 summieren, oder ohne train.
  • Ein Schlüssel, den writeback oder auto_retrain nicht hat, ein Eintrag in writeback.schemas, der kein Schema ist, oder ein min_confidence außerhalb von 0 bis 1. accept_license wird als Tippfehler von accept_licence gemeldet.
  • Ein aktivierter pre_annotation-Block, dessen field sich von writeback.field unterscheidet. Unter einem solchen Block würde kein Trainings-Vorlabel erscheinen.
  • Bei auto_retrain.enabled ein Trainer oder Schema, das nicht existiert, ein Schema, aus dem der Trainer nicht lernen kann, Parameter, die der Trainer nicht kennt oder die den falschen Typ haben, ein backend, das in backends fehlt, oder decision-calibrate auf einem anderen Backend als local.
  • Ein Parameter unter trainer_params, den der Trainer nicht kennt oder der den falschen Typ hat. Innerhalb von dp prüft die Kontrolle auch jeden Schlüssel und dessen Wertebereich.
  • Ein grace_seconds unter 0 oder keine Zahl, oder ein retain_runs oder split_seed unter 0 oder keine ganze Zahl.
  • Unter backends ein fehlender oder unbekannter type, ein Schlüssel, den der Typ nicht hat, ein ssh-Backend ohne host, ein gpus-Wert außer auto, none oder GPU-Indizes, ein Remote-Backend namens local oder ein model_training.backend, das nicht definiert ist.

potato training check testet ein Backend, bevor ein Lauf es nutzt. Für local gibt der Befehl das Gerät aus, das ein Lauf nutzen würde, z. B. local: this machine, Apple Metal (mps) auf einem Mac mit Apple Silicon. Für ein ssh-Backend meldet der Befehl die Python-Version, jede GPU mit ihrem freien Speicher, die GPU, die ein Lauf erhielte, und die Trainingspakete, die dem entfernten Interpreter fehlen. Die Schaltfläche Check auf der Training-Seite führt denselben Test aus, und --json gibt den Bericht als JSON aus:

bash
potato training check local config.yaml
potato training check gpu-server config.yaml

Ein Lauf, der von der Training-Seite oder über die API gestartet wird, wird beim Start erneut geprüft. Die Statuscodes stehen unter Routen.

Backends

Ein Lauf wird auf einem Backend ausgeführt, und das Menü Runs on der Training-Seite listet die konfigurierten Backends. model_training.backend setzt den Standard, und ein API-Lauf oder auto_retrain kann mit backend ein anderes Backend nennen. Potato kennt vier Backend-Typen:

TypWo der Trainer läuftWer den Trainer startet
localDiese Maschine, in einem Subprozess. Immer verfügbar und der Standard.Potato
sshEine andere Maschine, die Potato per ssh erreichen kann, etwa ein GPU-Server.Potato, über ssh
slurmDer Rechenknoten eines Clusters, als Batch-Job.Potato, per ssh zum Login-Knoten und dann sbatch
agentEine Maschine, die Potato nicht erreichen kann, etwa eine Workstation hinter NAT.Eine Person, mit potato training agent

Ein ssh-Backend nutzt schlüsselbasierte Authentifizierung mit eingeschaltetem BatchMode, sodass eine Passwortabfrage fehlschlägt, statt zu hängen. Ein slurm-Backend nutzt nur sbatch, squeue und scancel, dazu scontrol auf Clustern, die scontrol bereitstellen, und braucht ein remote_dir auf Speicher, den die Rechenknoten gemeinsam nutzen. Ein Agent-Backend braucht keinen eingehenden Port, weil der Agent jede Verbindung öffnet. Der Name local ist für diese Maschine reserviert, und decision-calibrate läuft nur auf local. Auf einem GPU-Server, SLURM-Cluster oder einer Maschine hinter NAT trainieren führt durch alle drei Remote-Typen mit ihren Schlüsseln.

Holdout-Scores

Jeder Lauf bewertet die eigenen Vorhersagen an den Items val und test gegen die aufgelösten menschlichen Labels. Metriknamen tragen den Split, wie in val_macro_f1 oder test_f1. Ein Item mit einem Label und ohne Vorhersage zählt als falsch, sodass das Auslassen schwieriger Items den Score eines Trainers nicht erhöhen kann. <split>_coverage meldet den Anteil der Items, die eine Vorhersage erhalten haben.

Der Hauptscore erscheint in der Spalte Held-out score der Runs-Tabelle, mit Split und Metrik im Tooltip. Der Hauptscore ist die Hauptmetrik des ersten Schemas auf dem ersten der Splits val und test, der Items hat. Hat ein Trainer den besten Checkpoint anhand des val-Scores gewählt, versucht Potato zuerst test, weil der Trainer den val-Score bereits gesehen hat. Die Fine-Tuning-Trainer, die die beste Epoche behalten, z. B. hf-text-classification, torchvision-detect und llm-sft, nehmen den Hauptscore von test. sklearn-text, setfit, rdkit-property, die Trainer für Entscheidungsmodelle und die Inference-Bridges wählen auf val nichts aus, und ein Lauf eines dieser Trainer nimmt den Hauptscore von val, sobald val Items hat. Ein Lauf ohne val- oder test-Items wird als „not scored“ angezeigt.

Die Hauptmetrik hängt vom Annotationstyp des Schemas ab, wie die folgende Tabelle für gängige Typen zeigt:

AnnotationstypHauptmetrik
radio, select, likertMacro-F1 (auch Genauigkeit, und mittlerer absoluter Fehler bei numerischen likert-Labels)
multiselectMicro-F1 (auch Macro-F1 und exakte Übereinstimmung)
spanF1 über Spans, die in Anfang, Ende und Label übereinstimmen
textToken-F1 (auch exakte Übereinstimmung)
pairwise im Modus binaryGenauigkeit über Items, die nicht als Unentschieden beantwortet wurden
rankingKendalls Tau
image_annotation, microscopy, geo_annotationCOCO-mAP bei IoU 0,5 (map_50), wobei auch map_50_95 aufgezeichnet wird
audio_annotationF1 über Segmente mit demselben Label bei IoU ab 0,5
speech_transcriptword_accuracy, 1 minus die Wortfehlerrate
volume_annotationmean_dice

Der Token-F1 belohnt eine Antwort, die die richtigen Wörter in falscher Reihenfolge nutzt, also lies den Token-F1 zusammen mit der exakten Übereinstimmung. Der Hauptscore der Detektion nutzt IoU 0,5 statt des Mittels von 0,5 bis 0,95, weil die Boxen zweier Annotatoren für ein Objekt sich selten bei IoU 0,9 überlappen. Die Upstream-Tabelle der Holdout-Scores listet die Metriken für jeden Annotationstyp.

Rückschreiben

Nach einem erfolgreichen Lauf speichert Potato die Vorhersagen des Laufs und schreibt die Vorhersagen, die vier Prüfungen bestehen, in das Feld predictions jedes Items oder in das Feld, das writeback.field nennt. Die Prüfungen laufen in dieser Reihenfolge, und der Bericht zum Rückschreiben des Laufs zählt jedes Überspringen:

  1. Items im Train-Split erhalten nie ein Vorlabel. Die Prüfung deckt den Train-Split jedes Laufs ab, der noch im Ledger steht, und ein Vorlabel, das ein früherer Lauf auf einem Item hinterlassen hat, wird entfernt, wenn ein späterer Lauf auf dem Item trainiert.
  2. Nur Schemas in writeback.schemas werden veröffentlicht. Die Liste ist standardmäßig leer, sodass das Einschalten des Trainings nichts ändert, was Annotatoren sehen.
  3. Abgelehnte Paare werden übersprungen. Ein Paar aus Item und Schema mit dem Urteil reject in der Modellprüfung wird nicht erneut angezeigt.
  4. Die Konfidenzschwelle gilt. Eine Vorhersage unter min_confidence wird gespeichert und nicht veröffentlicht.

Das Rückschreiben ändert nie ein menschliches Label, weil Vorhersagen in die Item-Daten gehen und Annotationen im Benutzerzustand jedes Annotators leben. Ein Vorlabel erscheint als vorausgefüllte Antwort, die der Annotator bearbeiten kann, z. B. eine Auswahl als gewählte Option und eine Freitextantwort im Textfeld des Schemas, und ein pre_annotation-Block ist nicht nötig. Das Vorlabel wird erst dann zur Antwort des Annotators, wenn der Annotator das Vorlabel bearbeitet oder weitergeht. Ein Vorlabel, das niemand bearbeitet oder übergangen hat, wird deshalb nie zu einem Trainingslabel. Ein Paar aus Annotator und Item, das ai_support.holdout in die Vergleichsgruppe der Studie ohne Unterstützung einordnet, zeigt kein Vorlabel, und die für dieses Paar gesendeten Item-Daten lassen sowohl das writeback.field als auch das pre_annotation.field aus. Der Bericht zum Rückschreiben ist das Feld writeback von GET /admin/training/api/status, und die summary des Berichts nennt den Grund, warum ein Lauf nichts veröffentlicht hat.

Potato hält pro Item und Schema eine Vorhersage, und die Vorhersage eines neueren Laufs ersetzt die eines älteren. Wird die neuere Vorhersage gespeichert, aber nicht angezeigt, weil sie unter min_confidence liegt, leer ist oder abgelehnt wurde, wird das Vorlabel des älteren Laufs sofort vom Item entfernt, und der Bericht zum Rückschreiben zählt das Item in n_superseded_cleared. Vorlabels, die mit den Daten geliefert wurden, bleiben unberührt. Beim Start werden die gespeicherten Vorhersagen für die Schemas in writeback.schemas unter denselben vier Prüfungen wieder in die Item-Daten geschrieben, sodass Vorlabels einen Neustart überleben. Um die Vorlabels eines Laufs zurückzuziehen, sende DELETE /admin/training/api/runs/<run_id>/predictions. Die Anfrage entfernt die gespeicherten Vorhersagen und nimmt die Vorlabels von den Items.

Korrekturen und angeheftete Items trainieren immer mit

Zwei Arten von Items gehen in den train-Split jedes späteren Laufs, gleichgültig, welchen Platz das Item im stabilen Split hat:

  • Items, die ein Reviewer korrigiert hat. Ein Urteil correct in der Modellprüfung bedeutet, dass eine Person die Antwort des Modells korrigiert hat. Korrekturen stammen von den Items, bei denen das Modell am schlechtesten war, sodass ein korrigiertes Item in val oder test das Modell an einer Stichprobe seiner eigenen Fehler bewerten würde. Die Urteile accept und reject lassen ein Item dort, wo es war.
  • Angeheftete Items. Train on these neben einem Slice auf der Seite Catalog heftet die Items an, auf die der Slice passt, z. B. der eingebaute Slice model-found-nothing. Ein angeheftetes Item wird trainiert, sobald jemand das Item gelabelt hat.

Ein Lauf, der zurückgehaltene Items nach train verschoben hat, schreibt eine Warnung mit der Anzahl ins Log. Der Holdout-Score des Laufs wird dann ohne diese Items berechnet und ist nicht direkt mit dem Score eines früheren Laufs vergleichbar. Die Anzahlen stehen unter stats.forced_train in der manifest.json der Trainingsdaten.

Einen Split als Datensatzversion speichern

Keep this split as a dataset auf der Seite eines Laufs speichert die val-, test- oder train-Items des Laufs mit ihren abgestimmten menschlichen Labels als neue Version eines Datensatzes. Eine Datensatzversion ändert sich danach nie mehr, sodass spätere Modelle und LLM-Prompts an genau diesen Items verglichen werden können. Die Funktion braucht datasets.enabled: true. Jede gespeicherte Version enthält einen Split und beginnt bei null, statt zur letzten Version des Datensatzes hinzuzufügen. Die val-Items von Lauf 2, zu denen von Lauf 1 hinzugefügt, würden sonst Items enthalten, auf denen Lauf 2 trainiert hat. Die inputs jedes Beispiels lassen das Vorlabel-Feld aus, sodass eine Auswertung die Antwort des Modells nicht lesen kann.

Woher ein Vorlabel stammt

GET /admin/model-review enthält provenance für jedes Item in der Prüfwarteschlange, dessen Vorlabel ein Trainingslauf geschrieben hat. Für jedes Schema nennt der Eintrag die Lauf-ID, den Trainer und den Holdout-Score des Laufs. Vorlabels, die mit den Daten geliefert wurden, haben keinen Eintrag.

Wo Läufe gespeichert werden

Jeder Lauf hat ein Verzeichnis unter <task_dir>/<output_annotation_dir>/training/runs/<run_id>/:

PfadInhalt
bundle/Die Trainingsdaten: train.jsonl, val.jsonl, test.jsonl, unlabeled.jsonl mit include_unlabeled, labels.json, manifest.json und media/.
artifacts/model_card.json, predictions.jsonl und das Modell, z. B. model.pkl für sklearn-text oder ein Verzeichnis model/ für die Transformer-Trainer.
spec.jsonWas dem Lauf aufgetragen wurde.
events.jsonl, stdout.log, stderr.logDie Fortschrittsereignisse und die Ausgabe des Laufs.
agent.logDie eigene Ausgabe des Remote-Agents, bei ssh-Backends.
bundle.tar.gzDie Trainingsdaten, wie der Agent sie heruntergeladen hat, einschließlich Medien, bei Remote-Backends.

Das Lauf-Ledger liegt in der Projektdatenbank <task_dir>/project.sqlite und hält die Läufe, den Split jedes Items pro Lauf, die auf Training festgelegten Items, die gespeicherten Vorhersagen, Agent-Token und Pushes auf den Hugging Face Hub fest. retain_runs beschneidet das Ledger und beschneidet nie einen Lauf, dessen Vorhersagen noch gespeichert sind, sodass sich jedes Vorlabel auf den Lauf zurückführen lässt, der es geschrieben hat. Das Beschneiden lässt Lauf-Verzeichnisse auf der Platte.

Ein LoRA-Lauf speichert nur den Adapter, einige bis einige hundert Megabyte, und lädt das Basismodell über model_name, wenn der Lauf vorhersagt. Ein vollständiges Fine-Tuning speichert das ganze Modell. Setze die Umgebungsvariable POTATO_MODEL_CACHE auf ein Verzeichnis, und der Trainingsprozess nutzt dieses Verzeichnis für HF_HOME, TRANSFORMERS_CACHE und SENTENCE_TRANSFORMERS_HOME, sofern diese Variablen nicht schon gesetzt sind. Lege auf einer Air-Gap-Maschine die Modelle, die die Trainer nennen, vorab in diesem Verzeichnis ab.

Routen

Die Training-Seite und ihre JSON-Routen liegen unter /admin/training und brauchen Admin-Zugriff. Admin-Zugriff bedeutet eine Admin-Sitzung oder den Header X-API-Key. POST- und DELETE-Anfragen aus einem Browser müssen von derselben Origin stammen. Die OpenAPI-Spezifikation von Potato kennzeichnet alle 26 Trainings-Operationen mit x-potato-requires-config: model_training.enabled: true:

RouteZweck
GET /admin/trainingDie Training-Seite, mit Lauf-Formular, Lauf-Liste und Trainern.
GET /admin/training/runs/<run_id>Die Seite eines Laufs, mit Status, Metriken, Ereignissen und dem Split jedes Items.
GET /admin/training/api/trainersJeder Trainer, mit seinen Arten, Modalitäten, seiner Lizenz und seinem Installationsstatus.
GET /admin/training/api/schemasDie Schemas der Studie, die Art jedes Schemas und die Trainer, die zu jedem Schema passen.
GET /admin/training/api/statusDer aktuelle oder letzte Lauf, mit Zustand, Fortschritt, Metriken, Warnungen, skip_reason, writeback und queue.
GET /admin/training/api/runsLauf-Verlauf. ?limit= hat den Standardwert 50 und das Maximum 500.
POST /admin/training/api/runsStartet einen Lauf. 200, wenn der Lauf startet, und 202 mit queued: true und position, wenn der Lauf wartet.
GET /admin/training/api/runs/<run_id>Der Eintrag eines Laufs, dazu review, die Präzision der Vorhersagen des Laufs unter Prüfurteilen.
GET /admin/training/api/runs/<run_id>/eventsDas Ereignislog des Laufs. ?limit= hat den Standardwert 200.
POST /admin/training/api/runs/<run_id>/cancelBricht den laufenden oder einen wartenden Lauf ab. 400 für jede andere Lauf-ID.
DELETE /admin/training/api/runs/<run_id>/predictionsZieht die gespeicherten Vorhersagen und Vorlabels des Laufs zurück.
POST /admin/training/api/runs/<run_id>/push-to-hubVeröffentlicht das Modell des Laufs auf dem Hugging Face Hub. Siehe Auf dem Hugging Face Hub veröffentlichen.
POST /admin/training/api/runs/<run_id>/pin-splitSpeichert einen Split als Datensatzversion, mit dem Körper {"dataset": name, "split": "val"}.
GET /admin/training/api/backendsDie konfigurierten Backends.
POST /admin/training/api/backends/<name>/checkTestet ein Backend, wie es potato training check tut.
GET /admin/training/api/pinnedAngeheftete Items, nach Grund gruppiert.
POST /admin/training/api/pinnedHeftet Items an train, mit {"slice": name} oder {"instance_ids": [...]}.
DELETE /admin/training/api/pinnedLöst die aufgelisteten instance_ids, oder jedes angeheftete Item, wenn der Körper keine auflistet.

Eine Lauf-Anfrage nimmt trainer, schemas (eine Liste oder einen Namen), params, backend, accept_licence und queue entgegen. Jeder Trainer passt nur das erste genannte Schema an. Die folgende Anfrage startet einen sklearn-text-Lauf auf dem Schema sentiment des Beispiels:

bash
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  http://localhost:8000/admin/training/api/runs \
  -d '{"trainer": "sklearn-text", "schemas": ["sentiment"]}'

Ein abgewiesener Start nennt den Grund in error:

StatusWann
400Kein Trainer oder kein Schema im Körper, ein unbekannter Trainer oder ein unbekanntes Schema, ein Schema, aus dem der Trainer nicht lernen kann, ein Trainer, den das Projekt nicht ausführen kann, oder ein Trainer, dessen Lizenz bestätigt werden muss, mit needs_licence_ack: true, bis die Anfrage accept_licence: true setzt
409Ein Parameter, den der Trainer nicht kennt oder der den falschen Typ oder Wertebereich hat, ein unbekanntes Backend, decision-calibrate auf einem anderen Backend als local oder ein bereits laufender Lauf bei "queue": false
429Es warten bereits queue_max Läufe

Agent-Protokoll

model_training.enabled registriert auch die Routen, die ein Trainings-Agent aufruft, unter /api/training. Ein Agent sendet sein Token als Authorization: Bearer <token>, und ein fehlendes oder ungültiges Token erhält 401. Das Registrieren eines Backends braucht stattdessen den Admin-API-Schlüssel. potato/training/protocol.py beschreibt das Protokoll für alle, die einen neuen Agent schreiben, und potato/training/agent.py ist die Referenzimplementierung:

RouteZweck
POST /api/training/backends/registerStellt ein Agent-Token für einen Backend-Namen aus, mit X-API-Key. potato training register ruft diese Route auf.
POST /api/training/backends/<name>/heartbeatEin untätiger Agent meldet, dass er lebt. 403 für ein Token, das für ein anderes Backend ausgestellt wurde.
GET /api/training/jobs/next?wait=30Long Poll. 200 mit einem Job oder 204, wenn innerhalb von wait Sekunden kein Job eintrifft.
GET /api/training/jobs/<run_id>Der Job, die Spezifikation des Jobs und ob der Job abgebrochen wurde.
GET /api/training/jobs/<run_id>/bundleDie Trainingsdaten als tar.gz, einschließlich Medien. Die Route beantwortet Range-Anfragen, sodass ein Agent einen abgebrochenen Download bis zu fünfmal fortsetzt. 410, sobald das Bundle weg ist.
POST /api/training/jobs/<run_id>/progressFortschrittsereignisse. Ein Abbruch kommt in der Antwort zurück.
POST /api/training/jobs/<run_id>/predictionspredictions.jsonl. 413 oberhalb von max_predictions_mb.
POST /api/training/jobs/<run_id>/completeExit-Code und letzte Ereignisse.

QLoRA

llm-sft, llm-dpo, hf-text-classification, vlm-sft und reward-model nehmen lora: {quantize: 4bit} entgegen, das Setup von QLoRA (Dettmers et al., 2023). Das Basismodell wird mit 4-Bit-NF4-Gewichten und doppelter Quantisierung geladen, und ein LoRA-Adapter wird darauf in bf16 trainiert, wo die GPU bf16 unterstützt. quantize: 8bit ist die Zwischenstufe, und Vorhersagen laden das Basismodell auf dieselbe Weise.

yaml
model_training:
  trainer_params:
    llm-sft:
      model_name: Qwen/Qwen2.5-7B-Instruct
      lora: {quantize: 4bit}

Mit 4 Bit brauchen die Basisgewichte ein Viertel des Speichers, den dieselben Gewichte in bf16 brauchen, und Aktivierungen, der Adapter und der Optimizer-Zustand kommen obendrauf. Potato hat den Speicherbedarf eines QLoRA-Laufs auf einer GPU noch nicht gemessen, plane also anhand der eigenen Zeile „Peak GPU memory“ des Laufs. Jeder Transformer-Lauf schreibt diese Zeile am Ende ins Log. QLoRA braucht eine CUDA-GPU und einen bitsandbytes-Build für das CUDA dieser Maschine. Der Lauf prüft beides vor dem Laden des Modells, indem er auf der GPU einige Zahlen quantisiert, und wenn die beiden Versionen nicht zusammenpassen, nennt der Fehler beide Versionen. Das Tutorial zum Fine-Tuning von LLMs setzt QLoRA bei SFT- und DPO-Läufen ein.

Auf dem Hugging Face Hub veröffentlichen

Ein erfolgreicher Lauf, dessen Modell auf dem Potato-Server liegt, hat auf der Seite des Laufs Publish to the Hugging Face Hub. Der Push lädt das Modell und eine README.md als Modellkarte hoch, die aus dem Lauf erzeugt wird und Trainer, Basismodell und Adapter, Schemas und Labels, Split-Größen, den Holdout-Score samt dem Split, an dem er gemessen wurde, sowie Lauf- und Bundle-ID nennt. Die Karte eines dp-Laufs hat außerdem eine Zeile „Differential privacy“ mit dem verbrauchten Epsilon gegenüber dem Ziel, dem Delta und dem Train-Split, den die Garantie abdeckt. Die Zeile lässt das Ziel oder das Delta weg, wenn der Lauf den Wert nicht festgehalten hat.

Der Upload ist eine feste Liste von Dateien und nicht das ganze Verzeichnis artifacts/. Die Liste enthält die Gewichtsdateien, z. B. .safetensors, .bin, .pt und .pkl, die Konfigurations- und Tokenizer-Dateien im gespeicherten Verzeichnis model/, model_card.json mit lokalen Pfaden, die durch Dateinamen ersetzt sind, und calibration.json. Die Antworten des Modells auf den Items des Projekts (predictions.jsonl und raw_answers.jsonl bei den Entscheidungsmodellen) bleiben auf dem Server. Checkpoints, Optimizer-Zustand, die Kopien der Trainingsdaten, die einige Trainer anlegen (yolo_data/, wev_data/, laya_data/), und jeder Symlink, der aus artifacts/ hinauszeigt, bleiben ebenfalls außen vor.

Das Formular schützt auf mehrere Arten vor einem versehentlichen oder öffentlichen Push:

  • Du tippst den Repository-Namen zweimal ein, und der Push läuft nur, wenn beide Namen übereinstimmen.
  • Private ist standardmäßig angehakt. Solange Private angehakt ist, pusht Potato nicht in ein bestehendes öffentliches Repository, und die API antwortet 409.
  • Das Formular nennt eine Umgebungsvariable auf dem Server, standardmäßig HF_TOKEN, die ein Hugging-Face-Schreibtoken enthält. Das Token selbst wird nie in die Seite getippt. Der Name der Variable muss TOKEN enthalten oder mit HF_ beginnen, und der Wert muss mit hf_ beginnen, sodass der Wert keiner anderen Variable den Hub erreichen kann.
  • Kann der Hub nicht sagen, ob das Repository existiert, wird nichts gepusht, und ein Repository, das der Push angelegt hat, wird wieder gelöscht, wenn der Upload danach fehlschlägt.
  • Die Lizenz der Modellkarte ist die Hub-Lizenz-ID, die du angibst, etwa apache-2.0. Ohne Angabe sagt die Karte, dass die Lizenz des Basismodells gilt. Ein Trainer, dessen Lizenz bestätigt werden muss, etwa ultralytics-detect (AGPL-3.0), braucht dieselbe Bestätigung für den Push.
  • Ein Modell, das mit fetch_artifacts: false auf einem Remote-Host liegt oder auf einem Agent-Backend trainiert wurde, lässt sich nicht vom Server pushen, und der Fehler nennt, wo das Modell liegt.

Jeder Push ist ein Commit. Die Lauf-Seite listet jeden Push mit Repository, Sichtbarkeit und Commit, und das Ledger hält fest, wer den Push ausgeführt hat. Über die API nimmt der Körper von POST /admin/training/api/runs/<run_id>/push-to-hub die Felder repo_id, confirm_repo_id, private (Standard true), token_env (Standard HF_TOKEN), licence und accept_licence entgegen. Die Route liefert 201 mit dem Commit, 400 bei einer Abweichung, einer fehlenden Token-Variable, fehlenden Modelldateien oder einer unbestätigten Lizenz und 502, wenn der Hub ablehnt.

Fehlerbehebung

Die Einträge unten decken die Fehler ab, die bei einem ersten Setup am wahrscheinlichsten sind. Die Upstream-Liste zur Fehlerbehebung behandelt GPU-Treiber, CUDA-Builds und die trainerspezifischen Meldungen.

  • /admin/training liefert 404. model_training.enabled ist nicht true, und Potato registriert die Routen nur, wenn der Schlüssel true ist.
  • „Only N items in the train split“. Annotiere mehr Items oder senke min_instances in den params des Laufs. Der Train-Split enthält standardmäßig 70 % der gelabelten Items.
  • „Schema 'x' has 1 distinct label(s) in the data“. Ein Klassifikator braucht mindestens zwei verwendete Labels.
  • „No resolved annotations to train on“. Das im Lauf genannte Schema unterscheidet sich vom annotierten Schema, oder bisher hat niemand etwas annotiert.
  • Der Lauf hat keine Vorlabels veröffentlicht. Lies den Bericht writeback in GET /admin/training/api/status. „No predictions were published: write-back is not enabled for any schema“ bedeutet, dass writeback.schemas leer ist, und die Zähler der übersprungenen Vorhersagen zeigen, wie viele Vorhersagen jede Prüfung entfernt hat.
  • <trainer> does not take <name>. Ein Parameter im Lauf oder unter trainer_params ist falsch geschrieben oder gehört zu einem anderen Trainer. Die Meldung listet die Namen auf, die der Trainer akzeptiert.
  • Der Lauf wurde beendet, nachdem der Maschine der Speicher ausgegangen war. Der Prozess endete mit SIGKILL, meist durch den Out-of-Memory-Killer. Nutze ein kleineres Modell oder weniger Items, oder setze max_ram_mb, damit der Lauf innerhalb seines eigenen Limits fehlschlägt.
  • „Stopped: the run took longer than model_training.max_wall_seconds“. Der Lauf hat das Zeitlimit erreicht. Erhöhe max_wall_seconds oder trainiere auf weniger Items.
  • „The GPU ran out of memory“. Senke batch_size und erhöhe gradient_accumulation, um den effektiven Batch zu halten, setze lora: true oder lora: {quantize: 4bit}, oder setze für llm-sft gradient_checkpointing: true oder wähle ein kleineres Modell.
  • „ssh to host failed: Permission denied“. Potato führt ssh mit BatchMode=yes aus, daher muss ssh <host> true vom Konto des Servers aus ohne Abfrage gelingen. Ein Schlüssel mit Passphrase funktioniert, wenn ssh-agent den Schlüssel hält.
  • Ein Lauf auf einem Agent-Backend bleibt in der Warteschlange. Kein Agent mit diesem Backend-Namen fragt ab. Starte einen Agent mit potato training agent --backend <name> und prüfe das Token mit Check auf der Training-Seite.

Weiterführende Lektüre

Literatur