GLM-4.6V

GLM-4.6V

이 문서는 ZhipuAI의 GLM-4.6V 시리즈 멀티모달 모델을 SGLang으로 배포하고 호출하는 방법을 설명해요. 106B 크기의 GLM-4.6V(클라우드/고성능 클러스터용 파운데이션 모델)와 9B 크기의 GLM-4.6V-Flash(로컬 배포·저지연 애플리케이션용 경량 모델) 두 버전이 있어요. 훈련에서 컨텍스트 윈도우를 128K 토큰으로 확장했고, 비슷한 파라미터 규모의 모델 중 시각 이해에서 SoTA 성능을 달성해요. 특히 GLM 팀이 처음으로 네이티브 Function Calling 능력을 통합했어요. 원문 페이지에는 하드웨어 플랫폼과 배포 전략을 골라 명령을 자동 생성해 주는 대화형 선택기가 포함되어 있어요.

출처: 문서

본문

1. 모델 소개

GLM-4.6V 시리즈 모델은 두 버전을 포함해요: GLM-4.6V(106B)는 클라우드 및 고성능 클러스터 시나리오를 위해 설계된 파운데이션 모델이고, GLM-4.6V-Flash(9B)는 로컬 배포와 저지연 애플리케이션에 최적화된 경량 모델이에요. GLM-4.6V는 훈련에서 컨텍스트 윈도우를 128k 토큰으로 확장하며, 비슷한 파라미터 규모의 모델 중 시각 이해에서 SoTA 성능을 달성해요. 결정적으로, GLM 팀은 처음으로 네이티브 Function Calling 능력을 통합해 "시각 지각"과 "실행 가능한 동작" 사이의 간극을 효과적으로 메꾸며, 실제 비즈니스 시나리오의 멀티모달 에이전트를 위한 통합 기술 토대를 제공해요.

비슷한 모델 규모의 주요 멀티모달 벤치마크에서 SoTA 성능을 달성하는 것 외에도, GLM-4.6V는 여러 핵심 기능을 도입해요:

  • 네이티브 멀티모달 함수 호출 (Native Multimodal Function Calling) — 텍스트 변환 없이 이미지, 스크린샷, 문서 페이지를 도구 입력으로 직접 전달할 수 있는 비전 구동 도구 사용을 활성화해요. 시각 출력(차트, 검색 이미지, 렌더링된 페이지)은 해석되어 추론 체인에 통합돼요. 지각 → 이해 → 실행으로 이어지는 루프를 닫아요. 이 예시를 참고하세요.
  • 교차 이미지-텍스트 콘텐츠 생성 (Interleaved Image-Text Content Generation) — 복잡한 멀티모달 입력에서 고품질 혼합 미디어 생성을 지원해요. GLM-4.6V는 문서, 사용자 입력, 도구 검색 이미지를 포괄하는 멀티모달 컨텍스트를 받아 작업에 맞춰 조정된 일관된 교차 이미지-텍스트 콘텐츠를 합성해요. 생성 중 검색·검색 도구를 능동적으로 호출해 추가 텍스트와 시각을 수집·조합해 풍부하고 시각적으로 근거한 콘텐츠를 만들어요.
  • 멀티모달 문서 이해 (Multimodal Document Understanding) — 최대 128K 토큰의 다중 문서 또는 장문 문서 입력을 처리하고, 풍부한 서식의 페이지를 이미지로 직접 해석해요. 텍스트, 레이아웃, 차트, 표, 그림을 함께 이해해, 평문으로의 사전 변환 없이 복잡하고 이미지가 많은 문서를 정확히 이해할 수 있어요.
  • 프론트엔드 복제 및 시각 편집 (Frontend Replication & Visual Editing) — UI 스크린샷에서 픽셀 정확한 HTML/CSS를 재구성하고 자연어 구동 편집을 지원해요. 레이아웃, 컴포넌트, 스타일을 시각적으로 감지하고 깔끔한 코드를 생성하며, 간단한 사용자 지시로 반복적인 시각 수정을 적용해요.

2. SGLang 설치

SGLang은 여러 설치 방법을 제공해요. 하드웨어 플랫폼과 요구 사항에 가장 적합한 설치 방법을 선택할 수 있어요.

2.1 Docker 설치 (권장)

docker pull lmsysorg/sglang:latest

장점:

  • 기본 상태로 바로 사용 가능, 수동 환경 구성 불필요
  • 의존성 충돌 문제 회피
  • 서로 다른 환경 간 마이그레이션 용이

2.2 소스에서 빌드

최신 개발 버전을 사용하거나 커스텀 수정이 필요하면 소스에서 빌드할 수 있어요:

# Install SGLang using UV (recommended)
git clone https://github.com/sgl-project/sglang.git
cd sglang
uv venv
source .venv/bin/activate
uv pip install --prerelease=allow -e "python[all]" --index-url=https://pypi.org/simple
pip install nvidia-cudnn-cu12==9.16.0.29
# Install ffmpeg to support video input
sudo apt update
sudo apt install ffmpeg

사용 사례:

  • SGLang 소스 코드 커스터마이즈 및 수정 필요
  • 최신 개발 기능 사용 원함
  • SGLang 프로젝트 개발 참여

일반 설치 지침은 공식 SGLang 설치 가이드를 참고할 수도 있어요.

3. 모델 배포

3.1 기본 설정

대화형 명령 생성기 (Interactive Command Generator): 아래 대화형 설정 생성기를 사용해 배포 설정을 맞춤화하세요. 하드웨어 플랫폼, 모델 크기, 양자화 방법 등을 선택해 적절한 실행 명령을 생성하세요. (대화형 위젯은 원문 페이지에서 동작하므로, 기본 설정 기준 명령은 다음과 같아요.)

python -m sglang.launch_server \
  --model-path zai-org/GLM-4.6V \
  --host 0.0.0.0 \
  --port 30000

3.2 설정 팁

  • TTFT 최적화: SGLANG_USE_CUDA_IPC_TRANSPORT=1을 설정해 CUDA IPC로 멀티모달 특성을 전송하면 TTFT가 크게 개선돼요. 이는 추가 메모리를 소비하므로 --mem-fraction-static 및/또는 --max-running-requests를 조정해야 할 수 있어요. (추가 메모리는 이미지 크기 × 현재 실행 중 요청의 이미지 수에 비례해요.)
  • TP=8 설정: 텐서 병렬화(TP) 8을 사용할 때 비전 attention의 12개 헤드를 균등하게 나눌 수 없어요. --mm-enable-dp-encoder(위 생성기가 자동 처리)를 추가해 해결할 수 있어요.
  • 빠른 모델 로딩: 큰 모델(106B 버전 같은)에서는 --model-loader-extra-config='{"enable_multithread_load": "true","num_threads": 64}'를 사용해 모델 로딩을 빠르게 할 수 있어요.
  • 하드웨어 참고 사항:
    • H100 (FP8): 최상의 메모리 효율을 위해 FP8 체크포인트를 사용하세요.
    • A100 / H100 (BF16): 처리량과 GPU 메모리 사용량을 관리하려면 표준 멀티모달 파라미터를 사용하세요.
    • H200 / B200: 기본 상태로 실행되며, 전체 컨텍스트 길이 + 동시 이미지 및 비디오 처리를 지원해요.
  • 추가 멀티모달 파라미터:
    • --mm-attention-backend fa3: 멀티모달 attention 백엔드(Flash Attention 3) 지정.
    • --mm-feature-transport=cuda_ipc: 멀티모달 데이터 전송에 CUDA IPC 공유 메모리를 사용해 D2H 메모리 복사를 피하고 E2E 지연 시간을 크게 개선.

전체 멀티모달 최적화가 포함된 예시:

SGLANG_USE_CUDA_IPC_TRANSPORT=1 \
SGLANG_VLM_CACHE_SIZE_MB=0 \
python -m sglang.launch_server \
  --model-path zai-org/GLM-4.6V \
  --host 0.0.0.0 \
  --port 30000 \
  --trust-remote-code \
  --tp-size 8 \
  --enable-cache-report \
  --log-level info \
  --max-running-requests 64 \
  --mem-fraction-static 0.65 \
  --chunked-prefill-size 8192 \
  --attention-backend fa3 \
  --mm-attention-backend fa3 \
  --mm-enable-dp-encoder \
  --enable-metrics

4. 모델 호출

4.1 기본 사용법

기본 API 사용법과 요청 예시는 다음을 참고하세요:

4.2 고급 사용법

4.2.1 멀티모달 입력

GLM-4.6V는 OpenAI 호환 API로 이미지와 비디오 입력을 지원해요.

이미지 입력:

import subprocess

curl_command = f"""
curl -s http://localhost:{30000}/v1/chat/completions \\
  -H "Content-Type: application/json" \\
  -d '{
    "model": "default",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "image_url",
            "image_url": {
              "url": "https://github.com/sgl-project/sglang/blob/main/examples/assets/example_image.png?raw=true"
            }
          },
          {
            "type": "text",
            "text": "What is the image"
          }
        ]
      }
    ],
    "temperature": "0",
    "max_completion_tokens": "1000",
    "max_tokens": "1000"
  }'
"""

response = subprocess.check_output(curl_command, shell=True).decode()
print(response)
{"id":"b61596ca71394dd699fd8abd4f650c44","object":"chat.completion","created":1765259019,"model":"default","choices":[{"index":0,"message":{"role":"assistant","content":"The image is a logo featuring the text \"SGL\" (in a bold, orange-brown font) alongside a stylized icon. The icon includes a network-like structure with circular nodes (suggesting connectivity or a tree/graph structure) and a tag with \"</>\" (a common symbol for coding, web development, or software). The color scheme uses warm orange-brown tones with a black background, giving it a tech-focused, modern aesthetic (likely representing a company, project, or tool related to software, web development, or digital technology).<|begin_of_box|>SGL logo (stylized text + network/coding icon)<|end_of_box|>","reasoning_content":"Okay, let's see. The image has a logo with the text \"SGL\" and a little icon on the left. The icon looks like a network or a tree structure with circles, and there's a tag with \"</>\" which is a common symbol for coding or web development. The colors are orange and brown tones, with a black background. So probably a logo for a company or project named SGL, maybe related to software, web development, or a tech company.","tool_calls":null},"logprobs":null,"finish_reason":"stop","matched_stop":151336}],"usage":{"prompt_tokens":2222,"total_tokens":2448,"completion_tokens":226,"prompt_tokens_details":null,"reasoning_tokens":0},"metadata":{"weight_version":"default"}}

비디오 입력:

import subprocess

curl_command = f"""
curl -s http://localhost:{30000}/v1/chat/completions \\
  -H "Content-Type: application/json" \\
  -d '{
    "model": "default",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "video_url",
            "video_url": {
              "url": "https://github.com/sgl-project/sgl-test-files/raw/refs/heads/main/videos/jobs_presenting_ipod.mp4"
            }
          },
          {
            "type": "text",
            "text": "What is in the video"
          }
        ]
      }
    ],
    "temperature": "0",
    "max_completion_tokens": "1000",
    "max_tokens": "1000"
  }'
"""

response = subprocess.check_output(curl_command, shell=True).decode()
print(response)
{"id":"520e0a079e5d4b17b82a6af619315a97","object":"chat.completion","created":1765259029,"model":"default","choices":[{"index":0,"message":{"role":"assistant","content":"The image is a still from a presentation by a man on a stage. He is pointing to a small pocket on his jeans and asking the audience what the pocket is for. The video is being shared by Evan Carmichael. The man then reveals that the pocket is for an iPod Nano.","reasoning_content":"Based on the visual evidence in the video, here is a breakdown of what is being shown:\n\n*   **Subject:** The video features a man on a stage, giving a presentation. He is wearing a black t-shirt and dark jeans.\n*   **Action:** The man is pointing to a pocket on his jeans. He is asking the audience a question about the purpose of this pocket.\n*   **Context:** The presentation is being filmed, and the video is being shared by \"Evan Carmichael,\" a well-known motivational speaker and content creator. The source of the clip is credited to \"JoshuaG.\"\n*   **Reveal:** The man then reveals the answer to his question. He pulls a small, white, rectangular device out of the pocket. He identifies this device as an \"iPod Nano.\"\n\nIn summary, the image is a still from a presentation where a speaker is explaining the purpose of the small pocket found on many pairs of jeans.","tool_calls":null},"logprobs":null,"finish_reason":"stop","matched_stop":151336}],"usage":{"prompt_tokens":30276,"total_tokens":30532,"completion_tokens":256,"prompt_tokens_details":null,"reasoning_tokens":0},"metadata":{"weight_version":"default"}}

4.2.2 사고 모드 (Thinking Mode)

GLM-4.6V는 Thinking 모드를 지원해요. 배포 중 reasoning parser를 활성화하세요:

python -m sglang.launch_server \
  --model zai-org/GLM-4.6V \
  --reasoning-parser glm45 \
  --tp 8 \
  --host 0.0.0.0 \
  --port 30000

사고 과정이 포함된 스트리밍:

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:30000/v1",
    api_key="EMPTY"
)

# Enable streaming to see the thinking process in real-time
response = client.chat.completions.create(
    model="zai-org/GLM-4.6V",
    messages=[
        {"role": "user", "content": "Solve this problem step by step: What is 15% of 240?"}
    ],
    temperature=0.7,
    max_tokens=2048,
    stream=True
)

# Process the stream
has_thinking = False
has_answer = False
thinking_started = False

for chunk in response:
    if chunk.choices and len(chunk.choices) > 0:
        delta = chunk.choices[0].delta

        # Print thinking process
        if hasattr(delta, 'reasoning_content') and delta.reasoning_content:
            if not thinking_started:
                print("=============== Thinking =================", flush=True)
                thinking_started = True
            has_thinking = True
            print(delta.reasoning_content, end="", flush=True)

        # Print answer content
        if delta.content:
            # Close thinking section and add content header
            if has_thinking and not has_answer:
                print("\n=============== Content =================", flush=True)
                has_answer = True
            print(delta.content, end="", flush=True)

print()

참고: reasoning parser는 모델의 단계별 사고 과정을 캡처해 모델이 결론에 도달하는 방식을 볼 수 있게 해줘요.

사고 모드 비활성화:

특정 요청에서 사고 모드를 비활성화하려면:

response = client.chat.completions.create(
    model="zai-org/GLM-4.6V",
    messages=[{"role": "user", "content": "What is the capital of France?"}],
    extra_body={"chat_template_kwargs": {"enable_thinking": False}}
)

4.2.3 도구 호출 (Tool Calling)

GLM-4.6V는 도구 호출 기능을 지원해요. tool call parser를 활성화하세요:

python -m sglang.launch_server \
  --model-path zai-org/GLM-4.6V \
  --reasoning-parser glm45 \
  --tool-call-parser glm45 \
  --tp 8 \
  --host 0.0.0.0 \
  --port 30000

Python 예시 (사고 과정 포함):

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:30000/v1",
    api_key="EMPTY"
)

# Define available tools
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city name"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit"
                    }
                },
                "required": ["location"]
            }
        }
    }
]

# Make request with streaming to see thinking process
response = client.chat.completions.create(
    model="zai-org/GLM-4.6V",
    messages=[
        {"role": "user", "content": "What's the weather in Beijing?"}
    ],
    tools=tools,
    temperature=0.7,
    stream=True
)

# Process streaming response
thinking_started = False
has_thinking = False
tool_calls_accumulator = {}

for chunk in response:
    if chunk.choices and len(chunk.choices) > 0:
        delta = chunk.choices[0].delta

        # Print thinking process
        if hasattr(delta, 'reasoning_content') and delta.reasoning_content:
            if not thinking_started:
                print("=============== Thinking =================", flush=True)
                thinking_started = True
            has_thinking = True
            print(delta.reasoning_content, end="", flush=True)

        # Accumulate tool calls
        if hasattr(delta, 'tool_calls') and delta.tool_calls:
            # Close thinking section if needed
            if has_thinking and thinking_started:
                print("\n=============== Content =================\n", flush=True)
                thinking_started = False

            for tool_call in delta.tool_calls:
                index = tool_call.index
                if index not in tool_calls_accumulator:
                    tool_calls_accumulator[index] = {
                        'name': None,
                        'arguments': ''
                    }

                if tool_call.function:
                    if tool_call.function.name:
                        tool_calls_accumulator[index]['name'] = tool_call.function.name
                    if tool_call.function.arguments:
                        tool_calls_accumulator[index]['arguments'] += tool_call.function.arguments

        # Print content
        if delta.content:
            print(delta.content, end="", flush=True)

# Print accumulated tool calls
for index, tool_call in sorted(tool_calls_accumulator.items()):
    print(f"🔧 Tool Call: {tool_call['name']}")
    print(f"   Arguments: {tool_call['arguments']}")

print()

출력 예시:

=============== Thinking =================
The user is asking about the weather in Beijing. I need to use the get_weather function to retrieve this information.
I should call the function with location="Beijing".
=============== Content =================

🔧 Tool Call: get_weather
   Arguments: {"location": "Beijing", "unit": "celsius"}

참고:

  • reasoning parser는 모델이 도구를 어떻게 사용하기로 결정하는지 보여줘요
  • 도구 호출은 함수 이름과 인자로 명확하게 표시돼요
  • 그런 다음 함수를 실행하고 결과를 다시 보내 대화를 이어갈 수 있어요

도구 호출 결과 처리:

# After getting the tool call, execute the function
def get_weather(location, unit="celsius"):
    # Your actual weather API call here
    return f"The weather in {location} is 22°{unit[0].upper()} and sunny."

# Send tool result back to the model
messages = [
    {"role": "user", "content": "What's the weather in Beijing?"},
    {
        "role": "assistant",
        "content": None,
        "tool_calls": [{
            "id": "call_123",
            "type": "function",
            "function": {
                "name": "get_weather",
                "arguments": '{"location": "Beijing", "unit": "celsius"}'
            }
        }]
    },
    {
        "role": "tool",
        "tool_call_id": "call_123",
        "content": get_weather("Beijing", "celsius")
    }
]

final_response = client.chat.completions.create(
    model="zai-org/GLM-4.6V",
    messages=messages,
    temperature=0.7
)

print(final_response.choices[0].message.content)
# Output: "The weather in Beijing is currently 22°C and sunny."

4.2.4 사고 예산 (Thinking Budget)

전체 추론 모드 활성화/비활성화(4.2.2절) 외에도 CustomLogitProcessor로 사고 토큰 수를 제한할 수 있어요. --enable-custom-logit-processor로 실행하고 요청에 Glm4MoeThinkingBudgetLogitProcessor를 전달하세요:

import openai
from sglang.srt.sampling.custom_logit_processor import Glm4MoeThinkingBudgetLogitProcessor

client = openai.Client(base_url="http://127.0.0.1:30000/v1", api_key="*")
response = client.chat.completions.create(
    model="zai-org/GLM-4.6V",
    messages=[{"role": "user", "content": "Describe this image briefly."}],
    max_tokens=1024,
    extra_body={
        "custom_logit_processor": Glm4MoeThinkingBudgetLogitProcessor().to_str(),
        "custom_params": {"thinking_budget": 512},
    },
)
print(response)

5. 벤치마크

5.1 텍스트 벤치마크: 지연 시간, 처리량, 정확도

5.2 멀티모달 벤치마크 - 지연 시간 및 처리량

5.3 멀티모달 정확도 벤치마크 - MMMU