Programmgesteuerte Feinabstimmung eines Foundation-Modell

Sie können eine Reihe von Foundation-Modelle in „ watsonx.ai “ programmgesteuert optimieren, um die Modelle an Ihren Anwendungsfall anzupassen.

Sie können ein Foundation-Modell mithilfe der folgenden Techniken programmgesteuert feinabstimmen:

  • Vollständige Feinabstimmung
  • Feinabstimmung mittels Low-Rank-Adaptation ( LoRA )
  • Feinabstimmung der quantisierten Low-Rank-Anpassung ( QLoRA )

Sie können jede beliebige Feinabstimmungsmethode verwenden, um benutzerdefinierte Foundation-Modelle zu optimieren.

Erforderliche Berechtigungen

Um Optimierungsexperimente durchzuführen, müssen Sie in einem Projekt über die Rolle „Admin“ oder „Editor“ verfügen.

Erforderliche Zugangsdaten

Sie müssen Anmeldedaten erstellen, um sich bei den APIs von watsonx.ai zu authentifizieren. Weitere Informationen finden Sie unter „Erstellen eines Bearer-Tokens “.

Datenformat

Tabellarisch: JSON, JSONL

Tabellarische Daten aus unterstützten Datenverbindungen. Weitere Informationen finden Sie unter „Datenformate “.

Hinweis: Sie können dieselben Trainingsdaten für ein oder mehrere Optimierungsversuche verwenden.
Datenmenge

50 bis 10.000 Beispielpaare aus Eingabe und Ausgabe. Die maximale Dateigröße beträgt 200 MB.

Möglichkeiten zur Weiterentwicklung

Sie können Foundation-Modelle mithilfe der folgenden Programmiermethoden optimieren:

Alternativ können Sie grafische Tools aus der Benutzeroberfläche von „ watsonx.ai “ verwenden, um Foundation-Modelle zu optimieren. Siehe Tuning Studio.

Unterstützte Foundation-Modelle

Um eine Liste der Foundation-Modelle zu erhalten, die eine vollständige Feinabstimmung unterstützen, verwenden Sie die folgende API-Anfrage:

curl -X GET \
  'https://cpd-<namespace-name>.apps.<OCP-domain>/ml/v1/foundation_model_specs?version=2025-02-20&filters=function_fine_tune_trainable'

Um eine Liste der Foundation-Modelle zu erhalten, die das Feinabstimmen von „ LoRA “ oder „ QLoRA “ unterstützen, verwenden Sie die folgende API-Anfrage:

curl -X GET \
  'https://cpd-<namespace-name>.apps.<OCP-domain>/ml/v1/foundation_model_specs?version=2025-02-20&filters=function_lora_fine_tune_trainable'

Siehe Auswahl eines Foundation-Modell zur Feinabstimmung.

Sie können „ LoRA “ nur bei nicht quantisierten Modellen und „ QLoRA “ nur bei quantisierten Modellen verwenden.

REST-API

Die grundlegenden Schritte, die Sie befolgen, sind bei jeder Technik weitgehend identisch. Die wichtigsten Unterschiede betreffen die Werte, die im Anfragetext für den Feinabstimmungs-Trainingsauftrag anzugeben sind, und werden in der folgenden Vorgehensweise hervorgehoben:

  1. Erstellen Sie eine Trainingsdatendatei, die zum Trainieren des Foundation-Modell verwendet werden soll.

    Weitere Informationen zu den Anforderungen an die Trainingsdatendatei finden Sie unter Datenformate für die Feinabstimmung Foundation-Modelle.

  2. Stellen Sie Ihre Trainingsdatendatei für die API bereit. Sie können eine Datenzuordnung erstellen

    Sie können eine der folgenden Aktionen ausführen:

    Sie verwenden die Asset-ID und die Angaben zur Trainingsdatendatei, wenn Sie den training_data_references Abschnitt des REST-API-Anfragetextes hinzufügen.

  3. Verwenden Sie die „ watsonx.ai “-API, um ein Trainingsexperiment zu erstellen. Siehe den Abschnitt „Erstellen eines Feinabstimmungsauftrags “.

    Sende die POST-Anfrage an den folgenden Endpunkt:

    curl --request POST 'https://cpd-<namespace-name>.apps.<OCP-domain>/ml/v1/fine_tunings?version=2025-02-14' \
      --header 'Accept: application/json' \
      --header 'Content-Type: application/json' \
      --header 'Authorization: Bearer ${TOKEN}' \
    

    Passen Sie das Experiment an, indem Sie Werte für verschiedene Parameter in der FineTuningParameters Nutzlast festlegen. Weitere Informationen finden Sie unter Auswahl eines Modells zur Optimierung und Parameter zur Feinabstimmung Foundation-Modelle.

    Legen auto_update_model Sie fest, true dass die generierte Ausgabe als Asset gespeichert wird, das Sie später bei der Bereitstellung des optimierten Foundation-Modell verwenden können. Andernfalls müssen Sie das durch das Experiment generierte optimierte Modell oder die Adapter im Repository-Dienst speichern, um eine zu erstellen asset_id , bevor Sie diese im Deployment verwenden können.

    Der folgende Beispiel-Anfragetext erstellt ein vollständiges Feinabstimmungsexperiment:

    {
      "project_id": "<project-id>",
      "name": "my fft experiment",
      "auto_update_model": true,
      "tuned_model_name": "my-fine-tuned-model",
      "parameters": {
        "base_model": {
          "model_id": "ibm/granite-3-1-8b-base" },
        "task_id": "classification",
        "num_epochs": 10,
        "learning_rate": 0.00001,
        "batch_size": 5,
        "max_seq_length": 1024,
        "accumulate_steps": 1,
        "gpu": {
          "num": 4
        }
      },
      "results_reference": {
        "location": {
          "path": "full_fine_tuning/results" },
        "type": "fs"
      },
      "training_data_references": [
        {
        "location": {
          "href":"/v2/assets/1e6591a2-c69d-4716-92e3-73e8c2270956project_id=<project-id>",
          "id":"1e6591a2-c69d-4716-92e3-73e8c2270956" },
        "type": "data_asset"
        }
      ]
    }
    

    Das folgende Beispiel zeigt die Ausgabe der API-Anfrage:

    {
      "entity": {
        "auto_update_model": true,
        "parameters": {
          "accumulate_steps": 1,
          "base_model": {
            "model_id": "ibm/granite-3-1-8b-base"
          },
          "batch_size": 5,
          "gpu": {
            "num": 4
          },
          "learning_rate": 0.00001,
          "max_seq_length": 1024,
          "num_epochs": 10,
          "response_template": "\n### Response:",
          "task_id": "classification",
          "verbalizer": "### Input:  \n\n### Response: "
        },
        "results_reference": {
          "location": {
            "path": "/projects/<project-id>/assets/full_fine_tuning/results",
            "notebooks_path": "/projects/<project-id>/assets/full_fine_tuning/results/63e98673-a2c0-45c1-8ac6-e26a47ec1914/notebooks",
            "training": "/projects/<project-id>/assets/full_fine_tuning/results/63e98673-a2c0-45c1-8ac6-e26a47ec1914",
            "training_status": "/projects/<project-id>/assets/full_fine_tuning/results/63e98673-a2c0-45c1-8ac6-e26a47ec1914/training-status.json",
            "assets_path": "/projects/<project-id>/assets/full_fine_tuning/results/63e98673-a2c0-45c1-8ac6-e26a47ec1914/assets"
          },
          "type": "fs"
        },
        "status": {
          "state": "pending"
        },
        "training_data_references": [
          {
            "location": {
              "href": "/v2/assets/1e6591a2-c69d-4716-92e3-73e8c2270956project_id=<project-id>",
              "id": "1e6591a2-c69d-4716-92e3-73e8c2270956"
            },
            "type": "data_asset"
          }
        ],
        "tuned_model": {
          "name": "my-fine-tuned-model-63e98673-a2c0-45c1-8ac6-e26a47ec1914"
        }
      },
      "metadata": {
        "created_at": "2025-02-14T20:49:03.959Z",
        "id": "63e98673-a2c0-45c1-8ac6-e26a47ec1914",
        "modified_at": "2025-02-14T20:49:03.959Z",
        "name": "my fft experiment",
        "project_id": "<project-id>"
      }
    }
    

    Der folgende Beispiel-Anfragetext erstellt ein Feinabstimmungsexperiment für „ LoRA “.

    {
      "project_id": "<project-id>",
      "name": "my LoRA experiment",
      "auto_update_model": true,
      "tuned_model_name": "my-lora-tuned-model",
      "parameters": {
        "base_model": {
          "model_id": "ibm/granite-3-1-8b-base" },
        "task_id": "classification",
        "num_epochs": 10,
        "learning_rate": 0.00001,
        "batch_size": 5,
        "max_seq_length": 4096,
        "accumulate_steps": 1,
        "gpu": {
          "num": 1
        },
        "peft_parameters": {
          "type": "lora",
          "rank": 8,
          "lora_alpha": 32,
          "lora_dropout": 0.05,
          "target_modules": ["all-linear"]
        }
      },
      "results_reference": {
        "location": {
          "path": "fine_tuning/results" },
        "type": "fs"
      },
      "training_data_references": [
        {
        "location": {
          "href":"/v2/assets/1e6591a2-c69d-4716-92e3-73e8c2270956project_id=<project-id>",
          "id":"1e6591a2-c69d-4716-92e3-73e8c2270956" },
        "type": "data_asset"
        }
      ]
    }
    

    Das folgende Beispiel zeigt die Ausgabe der API-Anfrage:

    {
      "entity": {
        "auto_update_model": true,
        "parameters": {
          "accumulate_steps": 1,
          "base_model": {
            "model_id": "ibm/granite-3-1-8b-base"
          },
          "batch_size": 5,
          "gpu": {
            "num": 1
          },
          "learning_rate": 0.00001,
          "max_seq_length": 1024,
          "num_epochs": 10,
          "peft_parameters": {
            "lora_alpha": 32,
            "lora_dropout": 0.05,
            "rank": 8,
            "target_modules": [
              "all-linear"
            ],
            "type": "lora"
          },
          "response_template": "\n### Response:",
          "task_id": "classification",
          "verbalizer": "### Input:  \n\n### Response: "
        },
        "results_reference": {
          "location": {
            "path": "/projects/<project-id>/assets/fine_tuning/results",
            "notebooks_path": "/projects/<project-id>/assets/fine_tuning/results/2491b2d9-bf96-4d3f-9ea7-8604861471e1/notebooks",
            "training": "/projects/<project-id>/assets/fine_tuning/results/2491b2d9-bf96-4d3f-9ea7-8604861471e1",
            "training_status": "/projects/<project-id>/assets/fine_tuning/results/2491b2d9-bf96-4d3f-9ea7-8604861471e1/training-status.json",
            "assets_path": "/projects/<project-id>/assets/fine_tuning/results/2491b2d9-bf96-4d3f-9ea7-8604861471e1/assets"
          },
          "type": "fs"
        },
        "status": {
          "state": "pending"
        },
        "training_data_references": [
          {
            "location": {
              "href": "/v2/assets/1e6591a2-c69d-4716-92e3-73e8c2270956?project_id=<project-id>",
              "id": "1e6591a2-c69d-4716-92e3-73e8c2270956"
            },
            "type": "data_asset"
          }
        ],
        "tuned_model": {
          "name": "my-lora-tuned-model-2491b2d9-bf96-4d3f-9ea7-8604861471e1"
        }
      },
      "metadata": {
        "created_at": "2025-02-14T19:47:36.629Z",
        "id": "2491b2d9-bf96-4d3f-9ea7-8604861471e1",
        "modified_at": "2025-02-14T19:47:36.629Z",
        "name": "My LoRA experiment",
        "project_id": "<project-id>"
      }
    }
    

    Der folgende Beispiel-Anfragetext erstellt ein Feinabstimmungsexperiment für „ QLoRA “.

    {
      "project_id": "<project-id>",
      "name": "my QLoRA experiment",
      "auto_update_model": true,
      "tuned_model_name": "my-qlora-tuned-model",
      "parameters": {
        "base_model": {
          "model_id": "meta-llama/llama-3-1-70b-gptq" },
        "task_id": "classification",
        "num_epochs": 10,
        "learning_rate": 0.00001,
        "batch_size": 5,
        "max_seq_length": 1024,
        "accumulate_steps": 1,
        "gpu": {
          "num": 1
        },
        "peft_parameters": {
          "type": "qlora",
          "rank": 8,
          "lora_alpha": 32,
          "lora_dropout": 0.05,
          "target_modules": []
        }
      },
      "results_reference": {
        "location": {
          "path": "fine_tuning/results" },
        "type": "fs"
      },
      "training_data_references": [
        {
        "location": {
          "href":"/v2/assets/1e6591a2-c69d-4716-92e3-73e8c2270956project_id=<project-id>",
          "id":"1e6591a2-c69d-4716-92e3-73e8c2270956" },
        "type": "data_asset"
        }
      ]
    }
    

    Das folgende Beispiel zeigt die Ausgabe der API-Anfrage:

    {
      "entity": {
        "auto_update_model": true,
        "parameters": {
          "accumulate_steps": 1,
          "base_model": {
            "model_id": "meta-llama/llama-3-1-70b-gptq"
          },
          "batch_size": 5,
          "gpu": {
            "num": 1
          },
          "learning_rate": 0.00001,
          "max_seq_length": 1024,
          "num_epochs": 10,
          "peft_parameters": {
            "lora_alpha": 32,
            "lora_dropout": 0.05,
            "rank": 8,
            "target_modules": [],
            "type": "qlora"
          },
          "response_template": "\n### Response:",
          "task_id": "classification",
          "verbalizer": "### Input:  \n\n### Response: "
        },
        "results_reference": {
          "location": {
            "path": "/projects/<project-id>/assets/fine_tuning/results",
            "notebooks_path": "/projects/<project-id>/assets/fine_tuning/results/2491b2d9-bf96-4d3f-9ea7-8604861471e1/notebooks",
            "training": "/projects/<project-id>/assets/fine_tuning/results/2491b2d9-bf96-4d3f-9ea7-8604861471e1",
            "training_status": "/projects/<project-id>/assets/fine_tuning/results/2491b2d9-bf96-4d3f-9ea7-8604861471e1/training-status.json",
            "assets_path": "/projects/<project-id>/assets/fine_tuning/results/2491b2d9-bf96-4d3f-9ea7-8604861471e1/assets"
          },
          "type": "fs"
        },
        "status": {
          "state": "pending"
        },
        "training_data_references": [
          {
            "location": {
              "href": "/v2/assets/1e6591a2-c69d-4716-92e3-73e8c2270956?project_id=<project-id>",
              "id": "1e6591a2-c69d-4716-92e3-73e8c2270956"
            },
            "type": "data_asset"
          }
        ],
        "tuned_model": {
          "name": "my-qlora-tuned-model-2491b2d9-bf96-4d3f-9ea7-8604861471e1"
        }
      },
      "metadata": {
        "created_at": "2025-02-14T19:47:36.629Z",
        "id": "2491b2d9-bf96-4d3f-9ea7-8604861471e1",
        "modified_at": "2025-02-14T19:47:36.629Z",
        "name": "My QLoRA experiment",
        "project_id": "<project-id>"
      }
    }
    

Verwendung benutzerdefinierter Parameter

Die Feinabstimmungs-API akzeptiert ein optionales custom Objekt in der Trainings-Payload, mit dem Sie beliebige Parameter direkt an den zugrunde liegenden Trainer (fms-hf-tuning) übergeben können. Diese erweiterte Funktion bietet erfahrenen Benutzern, die Trainingsparameter über die Standardoptionen hinaus konfigurieren müssen, mehr Flexibilität.

Wichtig: Die Verwendung benutzerdefinierter Parameter ist standardmäßig nicht aktiviert. Bevor Sie benutzerdefinierte Parameter verwenden, stellen Sie sicher, dass ein Cluster-Administrator diese Funktion aktiviert hat.

Verhalten

  • Wenn diese Option aktiviert ist : Die Werte in custom.parameters werden in die generierte Trainer-Konfiguration übernommen. Wenn ein Schlüssel mit einem Standardparameter in Konflikt steht, hat der benutzerdefinierte Wert Vorrang (Überschreibungsreihenfolge: custom.parameters > parameters).
  • Wenn deaktiviert : Das custom Objekt wird gespeichert und in API-Antworten zurückgegeben, hat custom.parameters jedoch keinen Einfluss auf das Training.
  • Validierung : Benutzerdefinierte Parameter werden nicht validiert. Inkompatible Parameter führen dazu, dass der Trainer zur Laufzeit fehlschlägt, nicht jedoch bei der Übermittlung.

Warnungen

Die API gibt Warnungen im system.warnings Array zurück, wenn benutzerdefinierte Parameter verwendet werden:

  • Funktion aktiviert : custom_parameters_warning - „Die Verwendung benutzerdefinierter Trainingsparameter erfolgt auf eigene Gefahr. Benutzerdefinierte Parameter überschreiben widersprüchliche Standard-Trainingsparameter und sind unter Umständen nicht kompatibel, was dazu führen kann, dass das Training fehlschlägt
  • Funktion deaktiviert : custom_parameters_unsupported_warning - „Benutzerdefinierte Trainingsparameter werden nicht unterstützt und ignoriert.“

Beispiel für eine Anfrage mit benutzerdefinierten Parametern

Das folgende Beispiel zeigt eine Anfrage zur Feinabstimmung von „ LoRA “, die benutzerdefinierte Parameter enthält:

{
  "name": "my-lora-fine-tuning",
  "space_id": "<space-id>",
  "auto_update_model": true,
  "parameters": {
    "base_model": {
      "model_id": "google/flan-t5-xl"
    },
    "task_id": "classification",
    "accumulate_steps": 1,
    "num_epochs": 5,
    "learning_rate": 0.00005,
    "batch_size": 16,
    "max_seq_length": 2048,
    "response_template": "\n### Response:",
    "verbalizer": "### Input:  \n\n### Response: ",
    "gpu": {
      "num": 1
    },
    "peft_parameters": {
      "type": "lora",
      "rank": 16,
      "target_modules": ["all-linear"],
      "lora_alpha": 32,
      "lora_dropout": 0.05
    },
    "gradient_checkpointing": true
  },
  "custom": {
    "parameters": {
      "data_formatter_template": "Custom ### Input:  \n\n### Response: ",
      "num_train_epochs": 10,
      "use_flash_attn": true
    }
  },
  "results_reference": {
    "connection": {},
    "location": {
      "path": "fine-tuning/experiment1"
    },
    "type": "container"
  },
  "training_data_references": [
    {
      "connection": {},
      "type": "data_asset",
      "location": {
        "href": "https://api.dataplatform.cloud.ibm.com/v2/assets/<asset-id>?space_id=<space-id>",
        "id": "<asset-id>"
      }
    }
  ]
}

Beispielantwort mit benutzerdefinierten Parametern**

Die Antwort enthält das custom Objekt und eine Warnung im system.warnings Array:

{
  "entity": {
    "custom": {
      "parameters": {
        "data_formatter_template": "Custom ### Input:  \n\n### Response: ",
        "num_train_epochs": 10,
        "use_flash_attn": true
      }
    },
    "parameters": {
      "accumulate_steps": 1,
      "base_model": { "model_id": "google/flan-t5-xl" },
      "batch_size": 16,
      "gpu": { "num": 1 },
      "gradient_checkpointing": true,
      "learning_rate": 0.00005,
      "max_seq_length": 2048,
      "num_epochs": 5,
      "peft_parameters": {
        "type": "lora",
        "rank": 16,
        "target_modules": ["all-linear"],
        "lora_alpha": 32,
        "lora_dropout": 0.05
      },
      "response_template": "\n### Response:",
      "task_id": "classification",
      "verbalizer": "### Input:  \n\n### Response: "
    },
    "results_reference": {
      "connection": {},
      "location": {
        "path": "fine-tuning/experiment1",
        "training": "fine-tuning/experiment1/<training-id>",
        "training_status": "fine-tuning/experiment1/<training-id>/training-status.json",
        "assets_path": "fine-tuning/experiment1/<training-id>/assets",
        "model_path": "fine-tuning/experiment1/<training-id>/model",
        "training_log": "fine-tuning/experiment1/<training-id>/data/fine_tunings/training.log"
      },
      "type": "container"
    },
    "status": {
      "state": "pending"
    },
    "training_data_references": [
      {
        "connection": {},
        "type": "data_asset",
        "location": {
          "href": "https://api.dataplatform.cloud.ibm.com/v2/assets/<asset-id>?space_id=<space-id>",
          "id": "<asset-id>"
        }
      }
    ],
    "tuned_model": {
      "name": "my-lora-fine-tuning-<training-id>"
    }
  },
  "metadata": {
    "created_at": "2025-10-14T20:17:50.445Z",
    "id": "<training-id>",
    "name": "my-lora-fine-tuning",
    "space_id": "<space-id>"
  },
  "system": {
    "warnings": [
      {
        "id": "custom_parameters_warning",
        "message": "Custom training parameters are used at your own risk. Custom parameters will override conflicting standard training parameters and may be incompatible, potentially causing training to fail."
      }
    ]
  }
}
Wichtig: Verwenden Sie benutzerdefinierte Parameter mit Bedacht. Sie werden ohne Überprüfung direkt an den zugrunde liegenden Trainer übergeben und können dazu führen, dass das Training fehlschlägt, wenn inkompatible Werte angegeben werden. Benutzerdefinierte Parameter haben bei Konflikten Vorrang vor Standardparametern.

Status des Trainingsauftrags prüfen

  1. Um den Status eines Trainingsauftrags zu überprüfen, können Sie die folgende Anfrage verwenden.

    Verwenden Sie den in der POST-Anfrage metadata.id zurückgegebenen Wert als Wert für den ID Parameter „path“ in der Anfrage.

    curl --request GET 'https://cpd-<namespace-name>.apps.<OCP-domain>/ml/v1/fine_tunings/2491b2d9-bf96-4d3f-9ea7-8604861471e1?project_id=<project-id>&version=2025-02-14'
    

    Informationen zur API-Referenz finden Sie unter „Get fine tuning job “.

    Das Abstimmexperiment ist beendet, wenn der Zustand ist completed.

    Wenn Sie dies "auto_update_model": true in die Anfrage aufgenommen haben, wird die Modell-Asset-ID des optimierten Modells oder Adapters im entity.tuned_model.id Feld der Antwort auf die GET-Anfrage aufgeführt. Notieren Sie sich die Modell-Asset-ID.

  2. Verwenden Sie die „ watsonx.ai “-API, um Ihr optimiertes Modell bereitzustellen.

    Um Ihr optimiertes Modell bereitzustellen, müssen Sie die für die verwendete Optimierungsmethode erforderlichen Schritte ausführen.

    • Low-Rank-Adaptation oder quantisierte Low-Rank-Adaptation: Führen Sie die folgenden Aufgaben aus:

      1. Erstellen Sie ein Basis Foundation-Modell.

        Das Modellobjekt definiert Metadaten für das Foundation-Modell, das als Basismodell verwendet wird. Siehe „Erstellen des Modell-Assets “.

      2. Stellen Sie das Foundation-Modell bereit.

        Sie benötigen eine eigene Instanz des Foundation-Modell, die bei der Inferenz verwendet werden kann. Siehe „Bereitstellung des Basismodells “.

      3. Stellen Sie das Low-Rank-Adapter-Asset bereit, das im Rahmen des Optimierungsexperiments generiert wurde.

        Setzen Sie Adapter ein, die die Gewichte des Basismodells zur Laufzeit anpassen können, um die Ausgabe an die jeweilige Aufgabe anzupassen. Siehe „Bereitstellung des Modell-Assets für den Adapter ‚ LoRA ‘ “.

    • Umfassende Feinabstimmung: Siehe „Bereitstellung feinabgestimmter Modelle “.

  3. Führen Sie eine Inferenz für das optimierte Foundation-Modell durch, indem Sie einen Inferenz-Endpunkt verwenden, der die eindeutige ID der Bereitstellung enthält, auf der das optimierte Modell gehostet wird.

Python

Sie können Foundation-Modelle in „ IBM “ ( watsonx.ai ) programmgesteuert mithilfe der TuneExperiment Klasse in der Bibliothek „ Python “ feinabstimmen. Weitere Informationen finden Sie unter „Arbeiten mit TuneExperiment “ und „ FineTuner “.

Die FoundationModelsManager Klasse verfügt über mehrere Hilfsmethoden, mit denen Sie eine Liste der anpassbaren Foundation-Modelle abrufen können. Weitere Informationen finden Sie unter Hilfsmethoden Foundation-Modell.

Um loszulegen, sehen Sie sich die folgenden Beispiel-Notebooks an:

Node.js

Sie können Foundation-Modelle in „ IBM “ ( watsonx.ai ) programmgesteuert mithilfe der createFineTuning Klasse in der Bibliothek „ Python “ feinabstimmen. Weitere Informationen finden Sie in den folgenden Ressourcen:

Weitere Informationen finden Sie im Code-Beispiel.