Microsoft MVP성태의 닷넷 이야기
닷넷: 2338. C# / Foundry Local - Phi-4-multimodal 모델을 사용하는 방법 [링크 복사], [링크+제목 복사],
조회: 135
글쓴 사람
정성태 (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)
13531정성태1/16/202411814닷넷: 2204. C# - TCP KeepAlive에 새로 추가된 Retry 옵션파일 다운로드1
13530정성태1/15/202411386닷넷: 2203. C# - Python과의 AES 암호화 연동파일 다운로드1
13529정성태1/15/202411284닷넷: 2202. C# - PublishAot의 glibc에 대한 정적 링킹하는 방법
13528정성태1/14/202411828Linux: 68. busybox 컨테이너에서 실행 가능한 C++, Go 프로그램 빌드
13527정성태1/14/202412011오류 유형: 892. Visual Studio - Failed to launch debug adapter. Additional information may be available in the output window.
13526정성태1/14/202412428닷넷: 2201. C# - Facebook 연동 / 사용자 탈퇴 처리 방법
13525정성태1/13/202411232오류 유형: 891. Visual Studio - Web Application을 실행하지 못하는 IISExpress
13524정성태1/12/202410775오류 유형: 890. 한국투자증권 KIS Developers OpenAPI - GW라우팅 중 오류가 발생했습니다.
13523정성태1/12/202411553오류 유형: 889. Visual Studio - error : A project with that name is already opened in the solution.
13522정성태1/11/202412401닷넷: 2200. C# - HttpClient.PostAsJsonAsync 호출 시 "Transfer-Encoding: chunked" 대신 "Content-Length" 헤더 처리
13521정성태1/11/202411362닷넷: 2199. C# - 한국투자증권 KIS Developers OpenAPI의 WebSocket Ping, Pong 처리
13520정성태1/10/202411281오류 유형: 888. C# - Unable to resolve service for type 'Microsoft.Extensions.ObjectPool.ObjectPool`....' [1]
13519정성태1/10/202410723닷넷: 2198. C# - Reflection을 이용한 ClientWebSocket의 Ping 호출파일 다운로드1
13518정성태1/9/202411918닷넷: 2197. C# - ClientWebSocket의 Ping, Pong 처리
13517정성태1/8/202410496스크립트: 63. Python - 공개 패키지를 이용한 위성 이미지 생성 (pystac_client, odc.stac)
13516정성태1/7/202411492닷넷: 2196. IIS - AppPool의 "Disable Overlapped Recycle" 옵션의 부작용
13515정성태1/6/202410553닷넷: 2195. async 메서드 내에서 C# 7의 discard 구문 활용 사례 [1]
13514정성태1/5/202410618개발 환경 구성: 702. IIS - AppPool의 "Disable Overlapped Recycle" 옵션
13513정성태1/5/202411323닷넷: 2194. C# - WebActivatorEx / System.Web의 PreApplicationStartMethod 특성
13512정성태1/4/202411906개발 환경 구성: 701. IIS - w3wp.exe 프로세스의 ASP.NET 런타임을 항상 Warmup 모드로 유지하는 preload Enabled 설정
13511정성태1/4/202411400닷넷: 2193. C# - ASP.NET Web Application + OpenAPI(Swashbuckle) 스펙 제공
13510정성태1/3/202411158닷넷: 2192. C# - 특정 실행 파일이 있는지 확인하는 방법 (Linux)
13509정성태1/3/202412497오류 유형: 887. .NET Core 2 이하의 프로젝트에서 System.Runtime.CompilerServices.Unsafe doesn't support netcoreapp2.0.
13508정성태1/3/202414903오류 유형: 886. ORA-28000: the account is locked
13507정성태1/2/202412491닷넷: 2191. C# - IPGlobalProperties를 이용해 netstat처럼 사용 중인 Socket 목록 구하는 방법파일 다운로드1
13506정성태12/29/202311330닷넷: 2190. C# - 닷넷 코어/5+에서 달라지는 System.Text.Encoding 지원
... 16  17  [18]  19  20  21  22  23  24  25  26  27  28  29  30  ...