Microsoft MVP성태의 닷넷 이야기
닷넷: 2338. C# / Foundry Local - Phi-4-multimodal 모델을 사용하는 방법 [링크 복사], [링크+제목 복사],
조회: 130
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 

C# / Foundry Local - Phi-4-multimodal 모델을 사용하는 방법

근래의 GPT나 notebooklm 같은 서비스들을 보면 채팅은 기본이고 그에 더해 이미지나 오디오 등의 자료도 함께 분석을 해주는데요, 이런 유의 모델을 "멀티 모달"이라고 하는 것 같습니다. 마침, 마이크로소프트의 Phi 시리즈에도 그런 모델이 최근에 공개됐는데요,

Welcome to the new Phi-4 models - Microsoft Phi-4-mini & Phi-4-multimodal
; https://techcommunity.microsoft.com/blog/educatordeveloperblog/welcome-to-the-new-phi-4-models---microsoft-phi-4-mini--phi-4-multimodal/4386037

microsoft/Phi-4-multimodal-instruct-onnx
; https://huggingface.co/microsoft/Phi-4-multimodal-instruct-onnx

아직 Foundry Local의 기본 목록에는 포함돼 있지 않지만 그래도 해당 모델이 처음부터 ONNX 포맷으로 제공되므로 그냥 다운로드하는 것만으로 사용 준비가 모두 완료된 것이나 다름없습니다.

// https://huggingface.co/microsoft/Phi-4-multimodal-instruct-onnx/tree/main
// 위의 경로에서 ./gpu 하위에 있는 디렉터리의 파일을 로컬에 다운로드

C:\temp> huggingface-cli download microsoft/Phi-4-multimodal-instruct-onnx --include gpu/* --local-dir .

위와 같이 실행하면 "gpu-int4-rtn-block-32" 디렉터리가 생성되는데요, 그걸 foundry cache 디렉터리로 복사하기만 하면 됩니다.

C:\foundry_cache\models> foundry cache location
💾 Cache directory path: C:\foundry_cache\models

C:\foundry_cache\models> dir /b
foundry.modelinfo.json
llama
gpu-int4-rtn-block-32
Qwen2.5-Math-1.5B-Instruct

foundry local은 디렉터리 이름을 기준으로 Model ID를 보여주므로,

C:\foundry_cache\models> foundry cache ls
Models cached on device:
   Alias                         Model ID
💾 Model was not found in catalogllama-3.2
💾 Model was not found in cataloggpu-int4-rtn-block-32
💾 Model was not found in catalogQwen2.5-Math-1.5B-Instruct

그보다는 인지하기 쉽게 "Phi-4-multimodal-instruct-onnx" 이름으로 바꾸는 게 좋겠죠? ^^ 또는 inference_model.json 파일을 생성해 바꾸시면 됩니다.

// 여기서는 어쨌든 디렉터리 이름도 바꾸면 좋으므로, 또한 inference_model.json 파일도 일부러 생성

C:\foundry_cache\models> type .\Phi-4-multimodal-instruct-onnx\inference_model.json
{
  "Name": "Phi-4-multimodal-instruct",
  "PromptTemplate": {
    "assistant": "{Content}",
    "prompt": "<|system|>You are a helpful assistant.<|end|><|user|>{Content}<|end|><|assistant|>"
  }
}

C:\foundry_cache\models> foundry cache ls
Models cached on device:
   Alias                         Model ID
💾 Model was not found in catalogllama-3.2
💾 Model was not found in catalogPhi-4-multimodal-instruct
💾 Model was not found in catalogQwen2.5-Math-1.5B-Instruct

코딩 없이 곧바로 테스트를 해볼까요? ^^

c:\temp> foundry model run Phi-4-multimodal-instruct
Model Phi-4-multimodal-instruct was found in the local cache.
🕘 Loading model...
🟢 Model Phi-4-multimodal-instruct loaded successfully

Interactive Chat. Enter /? or /help for help.

Interactive mode, please enter your prompt
> Would you introduce yourself?
🤖 Of course! I'm Phi, an AI developed by Microsoft. I'm here to help you with information, tasks, and general assistance. How can I help you today?

(비록 시간은 걸리지만) 잘 작동하는군요. ^^ 물론 C#에서도, 지난번의 소스 코드 중 alias만 바꿔서 사용할 수 있습니다.

using OpenAI;
using OpenAI.Chat;
using System.ClientModel;

namespace ConsoleApp1;

internal class Program
{
    // Install-Package OpenAI 
    static async Task Main(string[] args)
    {
        string ep = "http://localhost:5273/v1";
        string key = "OPENAI_API_KEY";
        string alias = "Phi-4-multimodal-instruct";

        OpenAIClientOptions options = new OpenAIClientOptions();
        options.NetworkTimeout = TimeSpan.FromMinutes(30);
        options.Endpoint = new Uri(ep);

        ApiKeyCredential akc = new ApiKeyCredential(key);
        ChatClient client = new(alias, akc, options);

        ChatCompletion completion = client.CompleteChat("Why is the sky blue?'");

        foreach (var message in completion.Content)
        {
            Console.WriteLine($"[{message.Kind}]: {message.Text}");
        }
    }
}

/* (109 초 정도가 걸린 후) 실행 결과: 
[Text]: The sky appears blue to the human eye because of a phenomenon known as Rayleigh scattering. This occurs when sunlight passes through Earth's atmosphere and collides with small particles and gas molecules. Blue light has a shorter wavelength and is scattered more easily and in greater amounts than other colors like red or yellow. This scattering causes the sky to appear blue to observers on the ground. This effect is most prominent when viewing the sky from an angle rather than looking straight down.
*/




그래도 명색이 multi-modal 모델인데 이미지도 첨부해 채팅 문맥으로는 써봐야죠. ^^ 이를 위해 OpenAI로는 이미지 첨부를 위해 다음과 같은 식으로 메시지 처리를 할 수 있는데요,

using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
using System.Diagnostics;

namespace ConsoleApp1;

internal class Program
{
    // Install-Package OpenAI 
    static void Main(string[] args)
    {
        string ep = "http://localhost:5273/v1";
        string key = "OPENAI_API_KEY";
        string alias = "Phi-4-multimodal-instruct";

        // ...[생략]...

        var file = File.ReadAllBytes("demo.png");
        var fileData =BinaryData.FromBytes(file);
        var messages = new List
        {
            ChatMessage.CreateSystemMessage(ChatMessageContentPart.CreateTextPart("You are a helpful assistant.")),
            ChatMessage.CreateUserMessage(
                ChatMessageContentPart.CreateImagePart(fileData, "image/png")),
            ChatMessage.CreateUserMessage(
                ChatMessageContentPart.CreateTextPart("What is this image?")),
        };

        ChatCompletion completion = client.CompleteChat(messages);

        // ...[생략]...
    }
}

/* 실행 결과:
Unhandled exception. System.ClientModel.ClientResultException: Service request failed.
Status: 500 (Internal Server Error)

   at OpenAI.ClientPipelineExtensions.ProcessMessage(ClientPipeline pipeline, PipelineMessage message, RequestOptions options)
   at OpenAI.Chat.ChatClient.CompleteChat(BinaryContent content, RequestOptions options)
   at OpenAI.Chat.ChatClient.CompleteChat(IEnumerable`1 messages, ChatCompletionOptions options, CancellationToken cancellationToken)
   at ConsoleApp1.Program.Main(String[] args) in C:\temp\ConsoleApp2\Program.cs:line 43
*/

그런데, 보다시피 실제로 해보면 저런 식의 오류가 발생합니다. 뭐가 잘못됐는지 모르겠군요, ^^ Foundry Local 측에서 뭔가 준비가 있어야 하는 것인지는 알 수 없으나, 암튼 이 부분은 정식 버전이 나온 후에 다시 해봐야겠습니다. ^^ (혹시 Foundry Local + Phi-4-multimodal-instruct 모델에 이미지 추론 테스트를 어떻게 할 수 있는지 아시는 분은 덧글 부탁드립니다.)




그래도 일단 결과는 보고 싶은데요, 그래서 OpenAI + Foundry Local 방식이 아닌, 직접 Microsoft.ML.OnnxRuntimeGenAI 라이브러리를 사용해 "Phi-4-multimodal-instruct-onnx" 모델로부터 이미지 추론을 해보겠습니다.

using Microsoft.ML.OnnxRuntimeGenAI;
using System.Diagnostics;

internal class Program
{
    // Install-Package Microsoft.ML.OnnxRuntimeGenAI

    private static void Main(string[] args)
    {
        string modelPath = @"C:\foundry_cache\models\Phi-4-multimodal-instruct-onnx";

        Config onnxConfig = new Config(modelPath);

        Console.Write("Loading model from " + modelPath + "...");
        using Model model = new(onnxConfig);

        using MultiModalProcessor processor = new(model);
        using var tokenizerStream = processor.CreateStream();

        string imgPath = Path.Combine(Environment.CurrentDirectory, "demo.png");
        Images images = Images.Load([imgPath]);

        string userInput = "describe this image <|image_1|>";
        string prompt = $"<|system|>You are a helpful assistant.<|end|><|user|>{userInput}<|end|><|assistant|>";

        var inputTensors = processor.ProcessImages(prompt, images);

        Tokenizer tokenizer = new Tokenizer(model);

        using GeneratorParams gParams = new GeneratorParams(model);
        gParams.SetSearchOption("max_length", 7680);
        gParams.SetInputs(inputTensors);

        using Generator generator = new(model, gParams);

        Console.Out.Write("\nAI:");
        while (!generator.IsDone())
        {
            generator.GenerateNextToken();
            var token = generator.GetSequence(0)[^1];
            Console.Out.Write(tokenizerStream.Decode(token));
            Console.Out.Flush();
        }

        images.Dispose();
        processor.Dispose();
        tokenizer.Dispose();
    }
}

위에서 예로 든 이미지는 이런데요,

multimodal_model_with_image_query_1.png

이에 대한 모델의 응답은 다음과 같습니다.

AI:This appears to be a screenshot of a message bubble from a messaging application. The message bubble contains various icons and text options for interacting with the message, such as options to message, chat, or help. The message bubble also includes a message icon and a message bubble icon.


또한, 저 이미지처럼 HTML 코드를 생성해 달라고 요청해도,

// https://github.com/microsoft/PhiCookBook/tree/main/md/02.Application/04.Vision/Phi4/CreateFrontend

string userInput = "Can you generate HTML + JS code about this image <|image_1|>";
string prompt = $"<|system|>You are a helpful assistant.<|end|><|user|>{userInput}<|end|><|assistant|>";

아래와 같은 응답을 받게 됩니다.

AI:The image appears to be a screenshot of a message bubble from a messaging application. To create a message bubble in HTML and JavaScript, you can use the following code snippet:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Message Bubble</title>
<style>
  .bubble {
    display: flex;
    flex-direction: row;
    align-items: flex-end;
  }
  .bubble .bubble-content {
    display: flex;
    flex-direction: row;
    align-items: flex-end;
  }
  .bubble .bubble-content .bubble-text {
    margin: 5px;
  }
</style>
</head>
<body>

<div class="bubble message" id="bubble1">
  <div class="bubble-content bubble-text">What's going on?</div>
</div>

<script>
  // JavaScript code to animate or modify the message bubble
</script>

</body>
</html>
```

This is a basic example and would need to be customized to fit the specific design and functionality you want to achieve.

결과는 썩 만족스럽지 못하군요. ^^; 아무래도 Local LLM 모델의 한계이지 않을까 싶습니다.




참고로, 이런 오류가 발생한다면?

Loading model from C:\foundry_cache\models\Phi-4-multimodal-instruct-onnx...Unhandled exception. Microsoft.ML.OnnxRuntimeGenAI.OnnxRuntimeGenAIException: Number of image tokens does not match the number of images. Please fix the prompt.
   at Microsoft.ML.OnnxRuntimeGenAI.MultiModalProcessor.ProcessImages(String prompt, Images images)
   at Program.Main(String[] args) in C:\temp\ConsoleApp1\Program.cs:line 31

사용자 Prompt에 이미지를 대체하는 placeholder가 없어서 그런 것입니다. 가령, 위에서 다룬 소스 코드의 경우 다음과 같이 "<|image_1|>" 부분을 빼먹게 되면,

Images images = Images.Load([imgPath]);

// string userInput = "describe this image <|image_1|>";
string userInput = "describe this image";
string prompt = $"<|system|>You are a helpful assistant.<|end|><|user|>{userInput}<|end|><|assistant|>";

var inputTensors = processor.ProcessImages(prompt, images);

"Number of image tokens does not match the number of images" 오류가 발생합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 6/20/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  [26]  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13327정성태4/19/202313135VS.NET IDE: 183. C# - .NET Core/5+ 환경에서 Fakes를 이용한 단위 테스트 방법
13326정성태4/18/202315937.NET Framework: 2109. C# - 닷넷 응용 프로그램에서 SQLite 사용 (System.Data.SQLite) [1]파일 다운로드1
13325정성태4/18/202313776스크립트: 48. 파이썬 - PostgreSQL의 with 문을 사용한 경우 연결 개체 누수
13324정성태4/17/202314268.NET Framework: 2108. C# - Octave의 "save -binary ..."로 생성한 바이너리 파일 분석파일 다운로드1
13323정성태4/16/202313812개발 환경 구성: 677. Octave에서 Excel read/write를 위한 io 패키지 설치
13322정성태4/15/202315393VS.NET IDE: 182. Visual Studio - 32비트로만 빌드된 ActiveX와 작업해야 한다면?
13321정성태4/14/202313167개발 환경 구성: 676. WSL/Linux Octave - Python 스크립트 연동
13320정성태4/13/202312914개발 환경 구성: 675. Windows Octave 8.1.0 - Python 스크립트 연동
13319정성태4/12/202313954개발 환경 구성: 674. WSL 2 환경에서 GNU Octave 설치
13318정성태4/11/202313556개발 환경 구성: 673. JetBrains IDE에서 "Squash Commits..." 메뉴가 비활성화된 경우
13317정성태4/11/202314046오류 유형: 855. WSL 2 Ubuntu 20.04 - error: cannot communicate with server: Post http://localhost/v2/snaps/...
13316정성태4/10/202311486오류 유형: 854. docker-compose 시 "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)" 오류 발생
13315정성태4/10/202313243Windows: 245. Win32 - 시간 만료를 갖는 컨텍스트 메뉴와 윈도우 메시지의 영역별 정의파일 다운로드1
13314정성태4/9/202314041개발 환경 구성: 672. DosBox를 이용한 Turbo C, Windows 3.1 설치 [1]
13313정성태4/9/202313537개발 환경 구성: 671. Hyper-V VM에 Turbo C 2.0 설치 [2]
13312정성태4/8/202312925Windows: 244. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (개선된 버전)파일 다운로드1
13311정성태4/7/202314449C/C++: 163. Visual Studio 2022 - DirectShow 예제 컴파일(WAV Dest)
13310정성태4/6/202313385C/C++: 162. Visual Studio - /NODEFAULTLIB 옵션 설정 후 수동으로 추가해야 할 library
13309정성태4/5/202314263.NET Framework: 2107. .NET 6+ FileStream의 구조 변화
13308정성태4/4/202314364스크립트: 47. 파이썬의 time.time() 실숫값을 GoLang / C#에서 사용하는 방법 [1]
13307정성태4/4/202313028.NET Framework: 2106. C# - .NET Core/5+ 환경의 Windows Forms 응용 프로그램에서 HINSTANCE 구하는 방법
13306정성태4/3/202313243Windows: 243. Win32 - 윈도우(cbWndExtra) 및 윈도우 클래스(cbClsExtra) 저장소 사용 방법
13305정성태4/1/202314716Windows: 242. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (쉬운 버전) [1]파일 다운로드1
13304정성태3/31/202314960VS.NET IDE: 181. Visual Studio - C/C++ 프로젝트에 application manifest 적용하는 방법
13303정성태3/30/202312901Windows: 241. 환경 변수 %PATH%에 DLL을 찾는 규칙
13302정성태3/30/202313987Windows: 240. RDP 환경에서 바뀌는 %TEMP% 디렉터리 경로
... 16  17  18  19  20  21  22  23  24  25  [26]  27  28  29  30  ...