다음을 통해 공유


ONNX Runtime Generative AI을 사용하여 Windows 앱에서 Phi3 및 기타 언어 모델을 사용해보세요.

이 문서에서는 Phi3 모델 및 ONNX Runtime Generative AI 라이브러리를 사용하여 간단한 생성 AI 채팅 앱을 구현하는 WinUI 3 앱을 만드는 방법을 안내합니다. LLM(대규모 언어 모델)을 사용하면 텍스트 생성, 변환, 추론 및 번역 기능을 앱에 추가할 수 있습니다. AI 및 기계 학습 모델을 Windows 앱에서 사용하는 방법에 대한 자세한 내용은 Windows 앱에서 AI 및 머신 러닝 모델 사용 시작하기를 참조하세요. ONNX 런타임 및 생성 AI에 대한 자세한 내용은 생성 AI와 함께 ONNX Runtime를 참조하세요.

AI 기능을 활용할 때는 책임 있는 생성 AI 애플리케이션 및 기능 개발과 관련된 Windows을 검토하는 것이 좋습니다.

ONNX Runtime은 무엇인가요?

ONNX Runtime 하드웨어 관련 라이브러리를 통합하는 유연한 인터페이스를 갖춘 플랫폼 간 기계 학습 모델 가속기입니다. ONNX Runtime PyTorch, Tensorflow/Keras, TFLite, scikit-learn및 기타 프레임워크의 모델과 함께 사용할 수 있습니다. 자세한 내용은 ONNX Runtime 웹사이트를 https://onnxruntime.ai/docs/에서 참조하세요.

필수 구성 요소

  • 디바이스에 개발자 모드가 설정되어 있어야 합니다. 자세한 내용은 장치를 개발용으로 활성화하기를 참조하세요.
  • .NET 데스크톱 개발 워크로드가 있는 Visual Studio 2022 이상.

새 C# WinUI 앱 만들기

Visual Studio에서 새 프로젝트를 만듭니다. 새 프로젝트 만들기 대화 상자에서 언어 필터를 "C#"으로 설정하고 프로젝트 형식 필터를 "winui"로 설정한 다음, 비어 있는 앱인 Packaged(데스크톱의 WinUI3) 템플릿을 선택합니다. 새 프로젝트의 이름을 "GenAIExample"로 지정합니다.

ONNX Runtime Generative AI Nuget 패키지에 대한 참조 추가

솔루션 탐색기에서 종속성을 마우스 오른쪽 버튼으로 클릭하고 NuGet 패키지 관리...를 선택합니다. NuGet 패키지 관리자에서 찾아보기 탭을 선택합니다. "Microsoft.ML.OnnxRuntimeGenAI.DirectML"을 검색하고 버전 드롭다운에서 최신 안정 버전을 선택한 다음 설치를 클릭합니다.

프로젝트에 모델 및 어휘 파일 추가

솔루션 탐색기에서 귀하의 프로젝트를 마우스 오른쪽 단추로 클릭하고, 추가 ->새 폴더를 선택합니다. 새 폴더 이름을 "Models"로 지정합니다. 이 예제에서는 https://huggingface.co/microsoft/Phi-3-mini-4k-instruct-onnx/tree/main/directml/directml-int4-awq-block-128모델을 사용합니다.

모델을 검색하는 방법에는 여러 가지가 있습니다. 이 연습에서는 명령줄 인터페이스(CLI)인 Hugging Face를 사용합니다. 다른 메서드를 사용하여 모델을 가져오는 경우 예제 코드에서 모델에 대한 파일 경로를 조정해야 할 수 있습니다. Hugging Face CLI를 설치하고 이를 사용하도록 계정을 설정하는 방법에 대한 자세한 내용은 CLI(명령줄 인터페이스)참조하세요.

CLI를 설치한 후 터미널을 열고 만든 Models 디렉터리로 이동하고 다음 명령을 입력합니다.

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

작업이 완료되면 다음 파일이 있는지 확인합니다. [Project Directory]\Models\directml\directml-int4-awq-block-128\model.onnx.

솔루션 탐색기에서 "directml-int4-awq-block-128" 폴더를 펼치고 폴더의 모든 파일을 선택합니다. 파일 속성 창에서 출력 디렉터리 복사를 "최신인 경우 복사"로 설정합니다.

모델과 상호 작용하는 간단한 UI 추가

이 예제에서는 프롬프트를 지정하기 위한 TextBox, 프롬프트를 제출하기 위한 단추, 모델에서 상태 메시지 및 응답을 표시하기 위한 TextBlock 포함하는 매우 간단한 UI를 만듭니다. 의 기본 MainWindow.xaml 요소를 다음 XAML로 바꾸십시오.

<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>

모델 초기화

Microsoft.ML.OnnxRuntimeGenAI 네임스페이스에 using 지시문을 추가합니다.

using Microsoft.ML.OnnxRuntimeGenAI;

ModelTokenizer대한 MainPage 클래스 정의 내에 멤버 변수를 선언합니다. 이전 단계에서 추가한 모델 파일의 위치를 설정합니다.

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

모델을 비동기적으로 초기화하는 도우미 메서드를 만듭니다. 이 메서드는 모델 디렉터리에 대한 경로를 전달하여 Model 클래스에 대한 생성자를 호출합니다. 그런 다음 모델로부터 새로운 토크나이저를 생성합니다.

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";
        });
    });
}

이 예제에서는 주 창이 활성화될 때 모델을 로드합니다. 페이지 생성자를 업데이트하여 Activated 이벤트에 대한 처리기를 등록하도록 합니다.

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

Activated 이벤트를 여러 번 발생시킬 수 있으므로 이벤트 처리기에서 모델을 초기화하기 전에 null인지 확인합니다.

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

모델에 프롬프트 제출

모델에 프롬프트를 제출한 다음 IAsyncEnumerable사용하여 결과를 호출자에게 비동기적으로 반환하는 도우미 메서드를 만듭니다.

이 방법에서는 루프 내에서 Generator 클래스가 사용되며, 각 패스마다 GenerateNextToken을 호출하여 입력 프롬프트를 기반으로 모델이 예측한 다음 몇 개의 문자(토큰이라 불림)를 가져옵니다. 이 루프는 생성기 IsDone 메서드가 true를 반환할 때까지 또는 토큰 "<|end|>", "<|system|>", "<|user|>" 중 하나가 수신되어 토큰 생성을 중지할 수 있음을 알릴 때까지 실행됩니다.

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;
    }
}

프롬프트를 제출하고 결과를 표시하는 UI 코드 추가

단추 클릭 처리기에서 먼저 모델이 null이 아닌지 확인합니다. 시스템 프롬프트와 사용자 프롬프트를 사용하여 프롬프트 문자열을 만든 후 InferStreaming을 호출하고, 응답의 각 부분으로 TextBlock을 업데이트합니다.

이 예제에 사용된 모델은 다음 형식으로 프롬프트를 수락하도록 학습되었습니다. 여기서 systemPrompt 모델이 동작하는 방법에 대한 지침이며 userPrompt 사용자의 질문입니다.

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

모델은 프롬프트 규칙을 문서화해야 합니다. 이 모델의 형식은 Huggingface 모델 카드 에 기록되어 있습니다.

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;
        }
    }
}

예제 실행

Visual Studio의 솔루션 플랫폼 드롭다운에서 대상 프로세서가 x64로 설정되어 있는지 확인합니다. ONNXRuntime 생성 AI 라이브러리는 x86을 지원하지 않습니다. 프로젝트를 빌드하고 실행합니다. TextBlock 모델이 로드되었음을 나타낼 때까지 기다립니다. 프롬프트 텍스트 상자에 프롬프트를 입력하고 제출 단추를 클릭합니다. 결과가 텍스트 블록을 점진적으로 채우는 것을 볼 수 있습니다.

또한 참조하십시오