Megosztás a következőn keresztül:


Ismerkedés a Phi3 és más nyelvi modellekkel a Windows-alkalmazásban a ONNX Runtime Generative AI

Ez a cikk végigvezeti egy Olyan WinUI 3-alkalmazás létrehozásán, amely Phi3-modellt és ONNX Runtime Generative AI kódtárat használ egy egyszerű generatív AI-csevegőalkalmazás implementálásához. A nagyméretű nyelvi modellek (LLM-ek) lehetővé teszik, hogy szöveggenerálási, átalakítási, érvelési és fordítási képességeket adjanak az alkalmazáshoz. Az AI- és gépi tanulási modellek Windows-alkalmazásban való használatáról további információt a AI használatának első lépései a Windowscímű témakörben talál. További információ az ONNX futtatókörnyezetről és a generatív AI-ről: Generative AI és ONNX Runtime.

Az AI-funkciók használatakor javasoljuk, hogy tekintse át: A Felelős Generatív AI Alkalmazások és Szolgáltatások Fejlesztése Windows-on.

Mi az a ONNX Runtime

ONNX Runtime egy platformfüggetlen gépi tanulási modellgyorsító, amely rugalmas felülettel rendelkezik a hardverspecifikus kódtárak integrálásához. ONNX Runtime a PyTorch, a Tensorflow/Keras, a TFLite, a scikit-learnés más keretrendszerek modelljeihez használható. További információért lásd a ONNX Runtime webhelyet a https://onnxruntime.ai/docs/címen.

Előfeltételek

  • Az eszköznek engedélyeznie kell a fejlesztői módot. További információ: Engedélyezze az eszközt a fejlesztéshez.
  • Visual Studio 2022 vagy újabb verzió a .NET asztali fejlesztési számítási feladattal.

Új C# WinUI-alkalmazás létrehozása

Hozzon létre egy új projektet a Visual Studióban. Az Új projekt létrehozása párbeszédpanelen állítsa a nyelvi szűrőt "C#" értékre, a projekttípus-szűrőt pedig "winui" értékre, majd válassza a Üres alkalmazás, Csomagolva (WinUI3 asztali verzió) sablont. Nevezze el az új projektet "GenAIExample" néven.

Hivatkozás hozzáadása a ONNX Runtime Generative AI Nuget-csomaghoz

A Megoldáskezelőbenkattintson a jobb gombbal Függőségek elemre, és válassza NuGet-csomagok kezelése...lehetőséget. A NuGet-csomagkezelőben válassza a Tallózás lapot. Keressen rá a "Microsoft.ML.OnnxRuntimeGenAI.DirectML" kifejezésre, válassza ki a legújabb stabil verziót a Verzió legördülő listában, majd kattintson a Telepítéselemre.

Modell- és szókincsfájl hozzáadása a projekthez

A Megoldáskezelőbenkattintson a jobb gombbal a projektre, és válassza a Hozzáadás>Új mappalehetőséget. Nevezze el az új mappát a "Models" névvel. Ebben a példában a modellt fogjuk használni https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128.

A modellek lekérésének számos különböző módja van. Ehhez az útmutatóhoz a Hugging Face parancssori felületet (CLI) fogjuk használni. Ha a modelleket egy másik módszerrel szerzi be, előfordulhat, hogy a példakódban módosítania kell a fájl elérési útját a modellhez. Az Hugging Face CLI telepítésével és a fiók beállításával kapcsolatos információkért lásd: Parancssori felület (CLI).

A parancssori felület telepítése után nyisson meg egy terminált, keresse meg a létrehozott Models könyvtárat, és írja be a következő parancsot.

huggingface-cli download microsoft/Phi-3-mini-4k-instruct-onnx --include directml/* --local-dir .

Ha a művelet befejeződött, ellenőrizze, hogy a következő fájl létezik-e: [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

A Megoldáskezelőbontsa ki a "directml-int4-awq-block-128" mappát, és jelölje ki a mappában lévő összes fájlt. A Fájl tulajdonságai panelen állítsa a Másolás a kimeneti könyvtárba opciót "Másolás, ha újabb" értékre.

Egyszerű felhasználói felület hozzáadása a modell használatához

Ebben a példában egy nagyon egyszerű felhasználói felületet hozunk létre, amely TextBox- tartalmaz egy kérdés megadásához, egy gombot a kérés elküldéséhez, valamint egy TextBlock az állapotüzenetek és a modell válaszainak megjelenítéséhez. Cserélje le az alapértelmezett StackPanel elemet MainWindow.xaml az alábbi XAML-ra.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column ="0">
        <TextBox x:Name="promptTextBox" Text="Compose a haiku about coding."/>
        <Button x:Name="myButton" Click="myButton_Click">Submit prompt</Button>
    </StackPanel>
    <Border Grid.Column="1" Margin="20">
        <TextBlock x:Name="responseTextBlock" TextWrapping="WrapWholeWords"/>
    </Border>
</Grid>

A modell inicializálása

A MainWindow.xaml.csadjon hozzá egy, a Microsoft.ML.OnnxRuntimeGenAI névtérhez tartozó használatirányelvet.

using Microsoft.ML.OnnxRuntimeGenAI;

Deklarálja a tagváltozókat a MainPage osztálydefinícióján belül a Model és a Tokenizerszámára. Adja meg az előző lépésekben hozzáadott modellfájlok helyét.

private Model? model = null;
private Tokenizer? tokenizer = null;
private readonly string ModelDir = 
    Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
        @"Models\directml\directml-int4-awq-block-128");

Hozzon létre egy segédmetódust a modell aszinkron inicializálásához. Ez a metódus meghívja a Modell osztály konstruktorát, és átadja a modellkönyvtár elérési útját. Ezután létrehoz egy új Tokenizer a modellből.

public Task InitializeModelAsync()
{

    DispatcherQueue.TryEnqueue(() =>
    {
        responseTextBlock.Text = "Loading model...";
    });

    return Task.Run(() =>
    {
        var sw = Stopwatch.StartNew();
        model = new Model(ModelDir);
        tokenizer = new Tokenizer(model);
        sw.Stop();
        DispatcherQueue.TryEnqueue(() =>
        {
            responseTextBlock.Text = $"Model loading took {sw.ElapsedMilliseconds} ms";
        });
    });
}

Ebben a példában a főablak aktiválásakor töltjük be a modellt. Frissítse a lapkonstruktort, hogy regisztráljon egy kezelőt az Aktivált eseményhez.

public MainWindow()
{
    this.InitializeComponent();
    this.Activated += MainWindow_Activated;
}

Az aktivált esemény többször is elindítható, ezért az eseménykezelőben ellenőrizze, hogy a modell null értékű-e az inicializálás előtt.

private async void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
    if (model == null)
    {
        await InitializeModelAsync();
    }
}

A kérés elküldése a modellnek

Hozzon létre egy segédmetódust, amely a kérést elküldi a modellnek, majd aszinkron módon visszaküldi az eredményeket a hívónak egy IAsyncEnumerablesegítségével.

Ebben a módszerben a Generator osztályt egy ciklusban használják, amely mindegyik körben meghívja a GenerateNextToken-t, hogy lekérje a modell által előrejelzett következő néhány karaktert, azaz a tokent, amely a bemeneti kérésen alapul. A hurok addig fut, amíg a generátor IsDone metódus igaz értéket nem ad vissza, vagy amíg bármelyik a következő jogkivonatok közül megérkezik: "<|end|>", "<|system|>", vagy "<|user|>", ami azt jelzi, hogy leállíthatjuk a jogkivonatok generálását.

public async IAsyncEnumerable<string> InferStreaming(string prompt)
{
    if (model == null || tokenizer == null)
    {
        throw new InvalidOperationException("Model is not ready");
    }

    var generatorParams = new GeneratorParams(model);

    var sequences = tokenizer.Encode(prompt);

    generatorParams.SetSearchOption("max_length", 2048);
    generatorParams.SetInputSequences(sequences);
    generatorParams.TryGraphCaptureWithMaxBatchSize(1);

    using var tokenizerStream = tokenizer.CreateStream();
    using var generator = new Generator(model, generatorParams);
    StringBuilder stringBuilder = new();
    while (!generator.IsDone())
    {
        string part;
        try
        {
            await Task.Delay(10).ConfigureAwait(false);
            generator.ComputeLogits();
            generator.GenerateNextToken();
            part = tokenizerStream.Decode(generator.GetSequence(0)[^1]);
            stringBuilder.Append(part);
            if (stringBuilder.ToString().Contains("<|end|>")
                || stringBuilder.ToString().Contains("<|user|>")
                || stringBuilder.ToString().Contains("<|system|>"))
            {
                break;
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
            break;
        }

        yield return part;
    }
}

Felhasználói felületi kód hozzáadása a kérés elküldéséhez és az eredmények megjelenítéséhez

A gomb kattintáskezelőben először ellenőrizze, hogy a modell nem null értékű-e. Hozzon létre egy utasításszöveget a rendszer és a felhasználó utasításaival, majd hívja meg a InferStreaming-t, frissítve a TextBlock-at a válasz minden részével.

Az ebben a példában használt modell a következő formátumban lett betanított kérések elfogadására, ahol systemPrompt a modell viselkedésére vonatkozó utasítások, userPrompt pedig a felhasználó kérdése.

<|system|>{systemPrompt}<|end|><|user|>{userPrompt}<|end|><|assistant|>

A modelleknek dokumentálniuk kell az utasítási konvencióikat. Ebben a modellben a formátum a Huggingface modellkártyánvan dokumentálva.

private async void myButton_Click(object sender, RoutedEventArgs e)
{
    responseTextBlock.Text = "";

    if(model != null)
    {
        var systemPrompt = "You are a helpful assistant.";
        var userPrompt = promptTextBox.Text;

        var prompt = $@"<|system|>{systemPrompt}<|end|><|user|>{userPrompt}<|end|><|assistant|>";
        
        await foreach (var part in InferStreaming(prompt))
        {
            responseTextBlock.Text += part;
        }
    }
}

A példa futtatása

A Visual Studióban a Megoldásplatformok legördülő listában győződjön meg arról, hogy a célprocesszor x64 értékre van állítva. Az ONNXRuntime Generative AI-kódtár nem támogatja az x86-ot. Hozza létre és futtassa a projektet. Várja meg, amíg a TextBlock jelzi, hogy a modell be lett töltve. Írjon be egy parancssort a parancssor szövegmezőbe, és kattintson a Küldés gombra. Látnia kell, hogy az eredmények fokozatosan kitöltik a szövegblokkot.

Lásd még: