Microsoft MVP성태의 닷넷 이야기
닷넷: 2338. C# / Foundry Local - Phi-4-multimodal 모델을 사용하는 방법 [링크 복사], [링크+제목 복사],
조회: 342
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 136  137  138  139  140  [141]  142  143  144  145  146  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1565정성태1/3/201423408.NET Framework: 404. 리플렉션을 이용해 닷넷 LicenseManager를 우회할 수 있는 사례
1564정성태1/3/201423975.NET Framework: 403. override 메서드가 정의된 타입의 인스턴스로 base 메서드를 호출하는 방법파일 다운로드1
1563정성태1/3/201429683오류 유형: 201. ASP.NET 웹 사이트를 IIS 7 이상의 환경에서 호스팅할 때 500 오류 발생
1562정성태1/2/201434337.NET Framework: 402. 카카오톡 PC 버전 실행 시 개발자 컴퓨터에서 Themida 오류 나는 문제 - 두 번째 이야기
1561정성태1/1/201444022오류 유형: 200. 카카오톡 PC 버전 실행 시 개발자 컴퓨터에서 Themida 오류 나는 문제 [2]
1560정성태1/1/201422539오류 유형: 199. Hyper-V - Checkpoint 생성 오류
1559정성태12/30/201325933.NET Framework: 401. windbg에서 확인해 보는 관리 힙의 인스턴스 구조 [2]
1558정성태12/29/201335555.NET Framework: 400. 눈으로 확인하는 LayoutKind 옵션 효과 [2]파일 다운로드1
1557정성태12/27/201324501.NET Framework: 399. LayoutKind 옵션에 대해 [2]
1556정성태12/27/201344731개발 환경 구성: 208. IIS + Node.js 환경 구성 [8]
1555정성태12/27/201331291개발 환경 구성: 207. IIS의 80 포트를 공유하는 응용 프로그램 만드는 방법 [1]
1554정성태12/26/201336634Windows: 78. 마음에 드는 윈도우 8.1 태블릿 - 델 베뉴 8 프로 5830 [4]
1553정성태12/26/201323678개발 환경 구성: 206. JNBridgePro와 한글 인코딩 문제파일 다운로드1
1552정성태12/25/201328947개발 환경 구성: 205. JNBridgePro를 이용해 C#에서 Java메서드 호출 테스트파일 다운로드1
1551정성태12/24/201324048.NET Framework: 398. tech-days 미니 토요세미나 - 3회 C#편 PPT 자료파일 다운로드1
1550정성태12/13/201326439Windows: 77. Windows 8 - 잠시 사용을 안하는 경우 화면 잠김 상태로 빠지는 문제
1549정성태12/13/201330001VC++: 73. IIS - ISAPI 필터 제작하는 방법 [2]
1548정성태12/10/201322579오류 유형: 198. C# - 제네릭 covariance/contravariance 사용할 때 컴파일 오류가 발생한다면?
1547정성태12/10/201332932.NET Framework: 397. C# - OCX 컨트롤에 구현된 메서드에 배열을 in, out으로 전달하는 방법파일 다운로드2
1546정성태11/28/201326207.NET Framework: 396. C# - 프로퍼티로 정의하면 필드보다 느릴까요? - windbg / ollydbg [3]
1545정성태11/28/201330087.NET Framework: 395. C# - 프로퍼티로 정의하면 필드보다 느릴까요? [3]
1544정성태11/27/201326728개발 환경 구성: 204. Visual Studio Online "Monaco" 서비스와 github 연동
1543정성태11/27/201331390오류 유형: 197. error MSB8008: Specified platform toolset (v120) is not installed or invalid. [1]
1542정성태11/27/201337072오류 유형: 196. The procedure entry point InitializeCriticalSectionEx could not be located in the dynamic link library KERNEL32.dll
1541정성태11/22/201338283.NET Framework: 394. async/await 사용 시 hang 문제가 발생하는 경우 [7]파일 다운로드1
1540정성태11/20/201326638개발 환경 구성: 203. Azure - WEB SITES 서비스 소개 [4]
... 136  137  138  139  140  [141]  142  143  144  145  146  147  148  149  150  ...