Microsoft MVP성태의 닷넷 이야기
닷넷: 2338. C# / Foundry Local - Phi-4-multimodal 모델을 사용하는 방법 [링크 복사], [링크+제목 복사],
조회: 477
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 3개 있습니다.)
개발 환경 구성: 748. Windows + Foundry Local - 로컬에서 AI 모델 활용
; https://www.sysnet.pe.kr/2/0/13943

닷넷: 2337. C# - Hugging Face에 공개된 LLM 모델을 Foundry Local에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/13954

닷넷: 2338. C# / Foundry Local - Phi-4-multimodal 모델을 사용하는 방법
; https://www.sysnet.pe.kr/2/0/13957




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/21/2025]

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

비밀번호

댓글 작성자
 




... 76  77  78  [79]  80  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11997정성태7/30/201921491.NET Framework: 850. C# - Excel(을 비롯해 Office 제품군) COM 객체를 제어 후 Excel.exe 프로세스가 남아 있는 문제 [2]파일 다운로드1
11996정성태7/25/201924507.NET Framework: 849. C# - Socket의 TIME_WAIT 상태를 없애는 방법파일 다운로드1
11995정성태7/23/201929255.NET Framework: 848. C# - smtp.daum.net 서비스(Implicit SSL)를 이용해 메일 보내는 방법 [2]
11994정성태7/22/201922946개발 환경 구성: 454. Azure 가상 머신(VM)에서 SMTP 메일 전송하는 방법파일 다운로드1
11993정성태7/22/201917721오류 유형: 561. Dism.exe 수행 시 "Error: 2 - The system cannot find the file specified." 오류 발생
11992정성태7/22/201919992오류 유형: 560. 서비스 관리자 실행 시 "Windows was unable to open service control manager database on [...]. Error 5: Access is denied." 오류 발생
11991정성태7/18/201916953디버깅 기술: 128. windbg - x64 환경에서 닷넷 예외가 발생한 경우 인자를 확인할 수 없었던 사례
11990정성태7/18/201918900오류 유형: 559. Settings / Update & Security 화면 진입 시 프로그램 종료
11989정성태7/18/201917687Windows: 162. Windows Server 2019 빌드 17763부터 Alt + F4 입력시 곧바로 로그아웃하는 현상
11988정성태7/18/201921071개발 환경 구성: 453. 마이크로소프트가 지정한 모든 Root 인증서를 설치하는 방법
11987정성태7/17/201926967오류 유형: 558. 윈도우 - KMODE_EXCEPTION_NOT_HANDLED 블루스크린(BSOD) 문제 [1]
11986정성태7/17/201918597오류 유형: 557. 드라이브 문자를 할당하지 않은 파티션을 탐색기에서 드라이브 문자와 함께 보여주는 문제
11985정성태7/17/201918557개발 환경 구성: 452. msbuild - csproj에 환경 변수 조건 사용 [1]
11984정성태7/9/201926789개발 환경 구성: 451. Microsoft Edge (Chromium)을 대상으로 한 Selenium WebDriver 사용법 [1]
11983정성태7/8/201915954오류 유형: 556. nodemon - 'mocha' is not recognized as an internal or external command, operable program or batch file.
11982정성태7/8/201916027오류 유형: 555. Visual Studio 빌드 오류 - result: unexpected exception occured (-1002 - 0xfffffc16)
11981정성태7/7/201919595Math: 64. C# - 3층 구조의 신경망(분류)파일 다운로드1
11980정성태7/7/201929218개발 환경 구성: 450. Visual Studio Code의 Java 확장을 이용한 간단한 프로젝트 구축파일 다운로드1
11979정성태7/7/201920215개발 환경 구성: 449. TFS에서 gitlab/github등의 git 서버로 마이그레이션하는 방법
11978정성태7/6/201919512Windows: 161. 계정 정보가 동일하지 않은 PC 간의 인증을 수행하는 방법 [1]
11977정성태7/6/201923930오류 유형: 554. git push - error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413 Request Entity Too Large
11976정성태7/4/201918289오류 유형: 553. (잘못 인증 한 후) 원격 git repo 재인증 시 "remote: HTTP Basic: Access denied" 오류 발생
11975정성태7/4/201926829개발 환경 구성: 448. Visual Studio Code에서 콘솔 응용 프로그램 개발 시 "입력"받는 방법
11974정성태7/4/201922909Linux: 22. "Visual Studio Code + Remote Development"로 윈도우 환경에서 리눅스(CentOS 7) C/C++ 개발
11973정성태7/4/201921347Linux: 21. 리눅스에서 공유 라이브러리가 로드되지 않는다면?
11972정성태7/3/201925521.NET Framework: 847. JAVA와 .NET 간의 AES 암호화 연동 [1]파일 다운로드1
... 76  77  78  [79]  80  81  82  83  84  85  86  87  88  89  90  ...