MiniMax-M3

MiniMax-M3

MiniMax-M3은 MiniMax의 네이티브 멀티모달 Mixture-of-Experts reasoning 모델이에요: 총 약 428B 파라미터, 토큰당 활성 약 23B(128 전문가, 토큰당 4 활성), 60 레이어, 텍스트·이미지·비디오에 걸친 1M-토큰 컨텍스트. 핵심 특징은 MiniMax Sparse Attention (MSA) — 1M 컨텍스트에서 낮은 비용을 유지하는 block-sparse "lightning indexer" 어텐션이에요(MiniMax 보고: 1M 컨텍스트에서 M2 대비 prefill 약 9×, decode 약 15× 속도 향상). 이 페이지는 NVIDIA Blackwell과 AMD Instinct에서 MXFP8 변형(MiniMaxAI/MiniMax-M3-MXFP8, 약 440 GB)을 서빙해요; NVIDIA Hopper(H200)에서는 완전 정밀도 bfloat16 빌드 MiniMaxAI/MiniMax-M3(§2.4)을 사용해요. MiniMax Community License로 배포돼요.

출처: 문서

본문

1. Model Introduction

MiniMax-M3은 MiniMax의 네이티브 멀티모달 Mixture-of-Experts reasoning 모델이에요: 총 약 428B 파라미터, 토큰당 활성 약 23B(128 전문가, 토큰당 4 활성), 60 레이어, 텍스트·이미지·비디오에 걸친 1M-토큰 컨텍스트. 핵심 특징은 MiniMax Sparse Attention (MSA) — 긴 컨텍스트 비용을 낮게 유지하는 block-sparse "lightning indexer" 어텐션이에요(MiniMax 보고: 1M 컨텍스트에서 M2 대비 prefill 약 9×, decode 약 15× 속도 향상). 이 페이지는 NVIDIA Blackwell과 AMD Instinct에서 MXFP8 변형(MiniMaxAI/MiniMax-M3-MXFP8, 약 440 GB)을 서빙해요; NVIDIA Hopper(H200)에서는 완전 정밀도 bfloat16 빌드 MiniMaxAI/MiniMax-M3(§2.4)을 사용해요. MiniMax Community License로 배포돼요.

SGLang이 서빙하는 주요 특징:

  • 멀티모달(비전 + 텍스트): OpenAI 호환 채팅 API를 통해 인터리브된 텍스트와 이미지를 받아요(MiniMaxM3SparseForConditionalGeneration으로 로드). URL과 base64를 통한 이미지 입력이 검증됐고, 비디오 입력은 여기서 테스트되지 않았어요.
  • Reasoning 모델: chain-of-thought를 <mm:think>...</mm:think>로 감싸서 출력해요. 항상 **--reasoning-parser auto**로 시작하세요 — chat template에서 올바른 parser를 자동 감지하고, SGLang이 태그를 제거해 trace를 message.reasoning_content에 별도로 반환해요.
  • 네이티브 tool calling: 커스텀 namespace-token XML 형식으로, 표준 OpenAI tool_calls로 파싱돼요. 항상 **--tool-call-parser auto**로 시작하세요 — chat template에서 올바른 parser를 자동 감지해요. 단일·병렬·중첩(object / array) 인자가 지원돼요.
  • Sparse attention: 대부분의 레이어가 M3의 "lightning indexer" block-sparse 어텐션(top-k 128-토큰 블록)을 사용해 컨텍스트 길이에 따라 decode 비용이 거의 일정하게 유지돼요. Blackwell에서는 MiniMax의 오픈소스 MSA kernel이 이 경로를 더 가속해요(§2.1).
  • 벤더 전반의 MXFP8 양자화: MXFP8 MoE 가중치가 NVIDIA Blackwell(B200 / B300 / GB200 / GB300)과 AMD Instinct MI350X/MI355X(gfx950 / CDNA4)에서 네이티브로 실행되며, 둘 다 하드웨어 MX-scaled matmul이 있어요. AMD MI300X/MI325X(gfx942 / CDNA3) — 하드웨어 MX 없음 — 에서는 SGLang이 로드 시 가중치를 block-fp8 [128,128]로 변환하고 튜닝된 ROCm 커널로 서빙해요(§2.3). 비전 타워는 양자화되지 않아요.

권장 생성 설정: 모델의 generation_config.jsontemperature 1.0 / top_p 0.95로 설정하며, SGLang이 자동 적용해요(기본 --sampling-defaults model). 모델 카드는 추가로 top_k 40을 제안하지만 그 값은 generation_config.json없어서 SGLang이 기본 적용하지 않아요. top_k는 요청당 샘플링 파라미터(런치 플래그 아님)이므로 원하면 호출마다 설정하세요(예: OpenAI 클라이언트로 extra_body={"top_k": 40}).

리소스: HuggingFace · MSA kernel

2. Configuration Tips

2.1 MSA sparse-attention fast path (Blackwell 사용자 권장)

MiniMax MSA (fmha_sm100, MIT-licensed)는 M3의 주요 sparse-attention 단계에 권장되는 Blackwell 커널로, 내장 Triton 대체보다 더 빠르고 메모리 효율적이에요. M3 dev 이미지에 미리 설치되어 배포돼요(lmsysorg/sglang:dev-minimax-m3, dev-cu13-minimax-m3 태그로도 공개) 그래서 위 Blackwell 레시피가 추가 설정 없이 자동으로 사용해요 — import fmha_sm100이 바로 동작하고 커널은 첫 사용 시 JIT 컴파일돼요. 그 외에는 순수하게 부가적이에요: 커스텀 이미지에서는 아래처럼 설치하면 레시피가 자동으로 사용하고, 없으면 같은 레시피가 내장 Triton 경로로 서빙해요. 이 교체는 수치적으로 동일하고(cosine ≥ 0.99999 vs Triton), decode는 CUDA-graph 캡처 가능을 유지하며, prefill TTFT는 8K–64K 컨텍스트에서 약 9–12% 떨어지고, MSA 경로는 Triton 경로가 OOM 나는 메모리 구성에서도 살아남아요.

요구사항 (MSA README에서):

  • GPU: NVIDIA SM100 제품군 — sm_100 (B200 / GB200)과 sm_103 (B300 / GB300).
  • Toolchain: PATH에 CUDA Toolkit nvcc ≥ 12.x(또는 CUDA_HOME 설정) — 커널은 첫 import에서 JIT 컴파일돼요.
  • Python: ≥ 3.10; OS: Linux — x86_64와 aarch64(Grace, 예: GB200 / GB300) 모두 동작; aarch64 빌드는 소스 수정이 필요 없어요.

MSA 설치(커스텀 이미지에서만) & 게이트 확인 (Python)

M3 Blackwell dev 이미지는 이미 MSA를 번들하므로 바로 게이트 확인으로 건너뛰어도 돼요. git clone / pip install 단계는 fmha_sm100이 없는 커스텀 이미지에서만 필요해요.

# Only on a custom image: --recursive pulls the CUTLASS submodule required for JIT compilation
git clone --recursive https://github.com/MiniMax-AI/MSA.git msa
cd msa && pip install .
# Verify the SGLang gate (True -> MSA engaged on this device; False -> Triton fallback):
python -c "from sglang.srt.layers.attention.minimax_sparse_ops.msa import msa_available; print(msa_available())"

첫 import가 커널을 JIT 컴파일하며, 차가운 nvcc 캐시에서는 30초에서 몇 분이 걸릴 수 있어요 — 이는 정상이지 걸린 것이 아니에요. 이후 서버 시작은 JIT 캐시를 사용해요.

다중 GPU 시작 전에 JIT 캐시를 웜업하세요. 차가운 캐시에서 여러 tensor-parallel rank가 MSA의 plan 커널을 JIT 컴파일하기 위해 경쟁하면 한 rank가 반쯤 연결된 모듈을 로드할 수 있어요(CUDA-graph 캡처에서 AttributeError: Module has no function 'plan'). 서버 시작 전에 게이트 확인 python -c "..."(또는 단일 프로세스 fmha_sm100_plan 호출)을 한 번 실행하세요 — 그러면 커널이 단일 프로세스로 컴파일되고 모든 rank가 웜 캐시를 사용해요.

게이트는 --attention-backend fa4를 요구해요(MSA의 sparse 블록은 128 토큰이므로 page size가 128이어야 해요). SGLang은 fa4 백엔드에 page_size를 자동으로 128로 강제해요 — M3 레시피가 사용하는 결합 --attention-backend fa4를 포함해서(#28976) — 그래서 아래 Blackwell 셀에서는 --page-size 128이 생략돼요. 언제든 env var SGLANG_DISABLE_MSA=1로 Triton 경로를 강제할 수 있어요. MSA는 Blackwell(SM100) 커널이라 AMD ROCm 경로에는 적용되지 않아요.

멀티모달(이미지) 서빙에서는 위 텍스트 레시피를 그대로 유지하세요 — --attention-backend fa4(MSA)는 그대로 — 비전 타워에는 --mm-attention-backend flashinfer_cudnn을 추가해요. 텍스트와 비전 타워 어텐션 백엔드는 독립적인 노브이며, MSA는 언어 모델 sparse 어텐션에만 닿고 이미지 처리에는 닿지 않아요.

2.2 Memory and workload tuning

NVIDIA Blackwell 레시피는 단일 노드 검증돼요: B200은 --tp 8, B300 / GB300은 --tp 4(4-GPU는 GB200 / GB300 단일 노드 상한이기도 해요). GB200 (sm_100, aarch64)은 추론 지원돼요 — 두 축 모두 위에서 검증됨(B200은 sm_100; GB300은 sm_103 aarch64) — 하지만 직접 벤치마킹되지 않았어요. AMD 레시피는 **8-GPU (--tp 8)**을 사용해요.

  • 메모리: --mem-fraction-static은 GPU 메모리를 가중치 + KV 풀용으로 예약하고, 나머지는 prefill 활성화 헤드룸이에요. 이 값은 GPU당 여유 메모리(카드 용량 − GPU당 가중치)에 비례하므로 TP 정도보다는 카드를 따라가요: B200은 0.65(180 GB — 가중치가 상주하면 헤드룸이 적음), 더 큰 메모리의 B300 / GB300은 0.75(AMD는 0.80). 더 낮은 TP는 GPU당 더 많은 가중치를 담으므로 더 타이트한 구성은 더 낮은 값이 필요해요 — B200은 --tp 4에서도 0.65가 필요해요. 검증된 값을 넘겨 올리는 것은 저동시성 단일 스트림 서빙에서만 괜찮고, 높은 동시성이나 긴 컨텍스트에서는 OOM이 나요.
  • 긴 컨텍스트 (32K+): --mem-fraction-static을 플랫폼 기본값으로 두고 --chunked-prefill-size16384로 올려요. sparse 어텐션 덕분에 디코드 TPOT은 컨텍스트 길이와 거의 무관하게 유지되며, 1K–128K 프롬프트가 검증됐어요.
  • 디코드 용량용 HiSparse: NVIDIA CUDA에서 HiSparse는 세 개의 dense 레이어를 GPU에 유지하고, 57개 sparse 레이어의 K/V 캐시를 pinned 호스트 메모리로 옮기며, 선택된 블록 ID를 swap-in 커널에 직접 공급해요. 출시된 4-KV-head 모델에는 --tp 4 이상, --disable-radix-cache, 그리고 device_buffer_size >= 2048을 사용해요. Triton 시작 명령에 --enable-hisparse --hisparse-config='{"device_buffer_size":4096,"host_to_device_ratio":2}'로 켜요.
  • TP 확장: B200은 --tp 8로 문서화됨; B300 / GB200 / GB300은 --tp 4(단일 노드 크로스 패밀리 공통 분모). 8-GPU B300 호스트에서는 더 많은 처리량 / KV 헤드룸을 위해 --tp 8로 올릴 수도 있어요.
  • 전문가 병렬화: 지연 시간을 처리량으로 바꾸려면 --ep을 추가해요(Expert Parallelism Deployment 참조). AMD에서는 --ep--tp와 같게 설정해요. EP > 1이면 shared-experts fusion이 자동 비활성화되고, AMD 표준 EP에서는 서버가 정확도 보존을 위해 --enable-aiter-allreduce-fusion도 자동 비활성화해요.
  • MiniMax config / processor 클래스를 로드하려면 --trust-remote-code가 필요해요.

2.3 AMD Instinct (ROCm)

MiniMax-M3은 아키텍처별로 두 코드 경로를 통해 AMD Instinct GPU에서 실행돼요 — 둘 다 자동으로 선택되며 어느 쪽이든 --quantization mxfp8을 전달해야 해요:

  • **MI350X / MI355X (gfx950, CDNA4)**에는 하드웨어 MX-scaled matmul이 있어서 MXFP8 가중치가 네이티브로 서빙돼요. SGLang이 체크포인트를 자동 감지하고, 패키징된 튜닝 MXFP8 구성과 함께 Triton MiniMax-M3 MoE 경로를 선택하며, 단일 노드 텐서 병렬화에 AITER fused all-reduce를 켜요. 시작 명령은 NVIDIA 레시피에서 Blackwell 전용 백엔드 플래그를 뺀 것이에요.
  • **MI300X / MI325X (gfx942, CDNA3)**에는 하드웨어 MX matmul이 없어요. SGLang이 로드 시 MXFP8 가중치를 block-fp8 [128,128]로 투명하게 변환한 다음 튜닝된 ROCm block-fp8 커널로 서빙해요(--attention-backend aiter, --moe-runner-backend triton; aiter 러너도 동작하며 약간 더 높게 점수를 매김). 차가운 시작에서 첫 생성이 AITER 구성을 JIT 컴파일해 기본 워밍업/HTTP 타임아웃을 초과할 수 있으므로 레시피가 --watchdog-timeout 3600 --skip-server-warmup을 추가해요. block-fp8 단계는 MXFP8의 네이티브 1×32 스케일링에 비해 작은 상대 오차만 추가해요 — GSM8K에서는 무시할 수 있어요(아래 벤치마크 카드 참조).

위 명령 패널에서 MI300X/MI325X 또는 MI350X/MI355X 타일을 선택해 각 경로의 정확한 시작 명령을 얻으세요.

AMD 레시피는 텍스트 워크로드(채팅, reasoning 분리, tool calling)에서 종단간 검증됐어요. 비전 타워는 ROCm에서 실행되지 않았으므로, AMD에서 이미지 입력하려면 Blackwell --mm-attention-backend flashinfer_cudnn 플래그를 생략하고 인코더가 ROCm 기본 백엔드를 쓰게 하며 비전은 그 경로에서 미검증으로 취급하세요.

2.4 Serving on Hopper (H200) with the bf16 build

MXFP8 커널은 Blackwell 전용이므로 Hopper(H200)는 완전 정밀도 bfloat16 빌드 MiniMaxAI/MiniMax-M3을 서빙해요. 위 Deploy 패널에서 H200 + BF16을 선택해 정확한 명령을 얻으세요 — --tp 8에서 실행돼요(bf16 가중치는 완전한 8-GPU 노드가 필요). SGLang이 Hopper용 올바른 백엔드를 자동 선택하므로 레시피는 최소로 유지돼요:

  • MoE 러너: Triton, bf16 가중치에 자동 선택.
  • 어텐션: page size 1의 FlashAttention-3. MSA(§2.1)는 Blackwell 커널이라 M3의 sparse 단계는 여기서 내장 Triton 경로로 실행돼요.
  • CUDA graph: 켜짐, 전체 디코드 그래프 캡처 포함.

고동시성 처리량(선택 사항). Hopper에서 sparse prefill은 별도의 eager forward로 Triton 경로에서 실행되는데, 이는 과도한 동시 부하에서 진행 중인 decode 배치를 잠시 멈추게 해요. --enable-mixed-chunk --chunked-prefill-size 2048을 추가하면 실행 중인 decode를 선점하는 대신 prefill 단계로 병합해, 8×H200에서 높은 동시성일 때 대략 +10%의 출력 처리량약 10% 더 낮은 중앙값 TPOT을 회복하며 정확도 변화는 없어요. 지연 시간에 민감한 저동시성 서빙에서는 꺼두세요.

8×H200에서 검증됨 — reasoning과 tool-call 자동 감지 및 장문 컨텍스트 생성 포함. Hopper의 prefill/decode 분리에는 §3.4를 참조하세요.

3. Advanced Usage

3.1 Reasoning

--reasoning-parser auto로 시작하세요(또는 위 PlaygroundParsers 카드에서 Reasoning Parser 토글). 그러면 <mm:think> trace가 message.reasoning_content에, 최종 답이 message.content에 별도로 들어가요 — 클라이언트 쪽 태그 제거가 필요 없어요.

from openai import OpenAI

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

response = client.chat.completions.create(
    model="MiniMaxAI/MiniMax-M3-MXFP8",
    messages=[{"role": "user", "content": "What is 15% of 240? Explain briefly."}],
    max_tokens=2048,
)

message = response.choices[0].message
print("=============== Reasoning ===============")
print(message.reasoning_content)
print("=============== Answer ==================")
print(message.content)
=============== Reasoning ===============
15% of 240. 15% = 0.15. 240 * 0.15 = 36. Quick check: 10% is 24, 5% is 12, 24 + 12 = 36.
=============== Answer ==================
15% of 240 is **36**.
(10% of 240 = 24, and 5% of 240 = 12; 24 + 12 = 36.)

스트리밍 시 trace는 delta.reasoning_content로, 답은 delta.content로 오므로 두 섹션을 실시간으로 분리 렌더링할 수 있어요:

response = client.chat.completions.create(
    model="MiniMaxAI/MiniMax-M3-MXFP8",
    messages=[{"role": "user", "content": "Solve step by step: what is 15% of 240?"}],
    max_tokens=2048,
    stream=True,
)

for chunk in response:
    if not chunk.choices:
        continue
    delta = chunk.choices[0].delta
    if getattr(delta, "reasoning_content", None):
        print(delta.reasoning_content, end="", flush=True)  # thinking stream
    if delta.content:
        print(delta.content, end="", flush=True)            # answer stream
print()

출력 예시:

[delta.reasoning_content — thinking stream]
Let me solve this step by step.

15% of 240
= 0.15 × 240
= 36

Let me verify: 10% of 240 = 24, 5% of 240 = 12, so 15% = 24 + 12 = 36. ✓

[delta.content — answer stream]
# Solving 15% of 240
## Step 1: Convert the percentage to a decimal
15% = 15/100 = 0.15
## Step 2: Multiply by 240
0.15 × 240 = 36
## Answer
**15% of 240 = 36**

3.2 Tool Calling

--tool-call-parser auto로 시작하세요(또는 위 PlaygroundParsers 카드에서 Tool Call Parser 토글) — chat template에서 M3의 tool-call parser를 자동 감지해요. M3는 커스텀 namespace-token XML 형식으로 tool call을 내보내요:

]<]minimax[>[<tool_call>
]<]minimax[>[<invoke name="get_weather">]<]minimax[>[<location>Beijing]<]minimax[>[</location>]<]minimax[>[</invoke>
]<]minimax[>[</tool_call>

parser는 이를 표준 OpenAI tool_calls 구조로 변환해요:

from openai import OpenAI

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

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"]},
                },
                "required": ["location"],
            },
        },
    }
]

response = client.chat.completions.create(
    model="MiniMaxAI/MiniMax-M3-MXFP8",
    messages=[{"role": "user", "content": "What's the weather in Beijing?"}],
    tools=tools,
)

message = response.choices[0].message
if message.tool_calls:
    for call in message.tool_calls:
        print(f"Tool: {call.function.name}")
        print(f"Args: {call.function.arguments}")
Tool: get_weather
Args: {"location": "Beijing"}

단일 플랫 호출을 넘어 parser는 다음도 지원해요:

  • 병렬 호출 — 단일 <tool_call> 래퍼 안에 여러 <invoke> 블록, 여러 message.tool_calls 항목으로 표면화.
  • 중첩 객체 인자object 타입 파라미터가 중첩 XML 태그로 출력되고 JSON 객체로 재구성.
  • 배열 인자array 타입 파라미터가 반복 <item> 자식을 사용하며 JSON 리스트로 재구성.

예를 들어 객체와 배열 파라미터를 가진 tool은 깨끗하게 왕복돼요:

create_event {"title": "Design sync", "attendees": ["alice", "bob"], "location": {"room": "R2", "floor": 3}}

tool 결과를 반환하려면 assistant의 tool_calls 턴과 일치하는 tool 메시지를 추가하고 모델에 계속하라고 요청하세요 — 후속 답변은 content뿐 아니라 reasoning_content에도 텍스트를 넣을 수 있으므로 둘 다 출력하세요.

3.3 Multimodal (Vision) Input

이미지는 표준 OpenAI image_url 콘텐츠 타입으로 들어와요. 비전 타워는 항상 로드되며, 이미지 서빙에는 Blackwell 배포 레시피에 --mm-attention-backend flashinfer_cudnn(비전 타워 백엔드)을 추가해요 — 텍스트 --attention-backend는 그대로예요(§2.1 참고). AMD에서는 --mm-attention-backend를 생략하고 인코더가 ROCm 기본(비전은 ROCm에서 미검증 — §2.3)을 쓰게 해요.

from openai import OpenAI

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

response = client.chat.completions.create(
    model="MiniMaxAI/MiniMax-M3-MXFP8",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://raw.githubusercontent.com/sgl-project/sglang/main/examples/assets/example_image.png"
                    },
                },
                {"type": "text", "text": "Describe this image in detail."},
            ],
        }
    ],
    max_tokens=1024,
)
print(response.choices[0].message.content)

출력 예시:

This image captures a striking and unusual urban scene on what appears to be a busy New York City street.

**Main Subject:**
A man stands on the rear bumper of a yellow taxi cab (an SUV-style cab, likely a Ford Escape hybrid), operating a full-sized ironing board set up across the back of the vehicle. He is wearing a bright yellow long-sleeved shirt and dark pants, and is actively ironing a blue garment, holding an iron in his right hand.

**Vehicles:**
- The yellow SUV taxi on the right is stationary, its rear hatch serving as the ironing platform.
- A second yellow taxi (a sedan) drives past on the left, captured with motion blur.

**Setting:**
Tall city buildings with classic urban architecture, an American flag, and white lane markings — a bustling downtown area, possibly Midtown Manhattan.

참고:

  • 서버가 외부 URL을 가져올 수 없으면 이미지를 base64 data:image/png;base64,... URI로 포함하세요 — SGLang이 서버 쪽에서 디코드해요.
  • 메시지당 여러 이미지가 지원되며, content 리스트에 image_url 항목을 더 추가하면 돼요.
  • reasoning과 tool calling은 멀티모달 요청에서도 같은 방식으로 동작해요 — 비전 프롬프트도 <mm:think> trace 및/또는 tool call을 만들 수 있어요.

3.4 Prefill-Decode (PD) Disaggregation

PD disaggregation은 prefill과 decode를 RDMA KV-transfer 패브릭(mooncake 또는 NIXL)으로 연결된 별도의 SGLang 서버에서 실행하고 PD 라우터가 앞에 서요. M3는 dense 모델보다 한 가지가 더 필요해요: 메인 KV cache와 함께, 모든 sparse "lightning-indexer" 레이어가 K-only 인덱스 버퍼를 유지하는데 그 버퍼도 decode 서버에 도달해야 해요 — 그렇지 않으면 sparse 어텐션이 낡은 상태를 읽어요. SGLang이 메인 KV와 함께 전송해요 — 같은 page 매핑을 재사용 — 그래서 M3는 추가 플래그 없이 올바르게 분리돼요.

지원 토폴로지(출시된 MiniMax-M3, 그 sparse 레이어가 모두 K-only):

  • 동일 텐서 병렬화 — prefill과 decode 서버가 같은 --tp로 실행.
  • 단일 파이프라인 스테이지 — PP = 1(기본값).
  • RDMA / InfiniBand를 통한 mooncake 또는 NIXL 전송 백엔드.

prefill 서버, 그다음 decode 서버를 시작해요 — 같은 레시피에 --disaggregation-mode decode와 부트스트랩 포트 없음. 하드웨어 선택:

Blackwell · MXFP8

Blackwell에서는 MXFP8 레시피(fa4, page size 128, deep_gemm MoE, MSA fast path(§2.1))가 자동 선택되므로 각 역할은 --disaggregation-* 플래그만 추가해요. 이는 검증된 2 × 4×B200 설정(노드 A에 TP4 prefill, 노드 B에 TP4 decode)이에요; --disaggregation-ib-device를 RDMA NIC에 가리키세요.

sglang serve \
  --model-path MiniMaxAI/MiniMax-M3-MXFP8 \
  --trust-remote-code \
  --reasoning-parser auto \
  --tool-call-parser auto \
  --tp 4 \
  --disaggregation-mode prefill \
  --disaggregation-transfer-backend nixl \
  --disaggregation-ib-device mlx5_0 \
  --host 0.0.0.0 --port 30000 \
  --disaggregation-bootstrap-port 8998
sglang serve \
  --model-path MiniMaxAI/MiniMax-M3-MXFP8 \
  --trust-remote-code \
  --reasoning-parser auto \
  --tool-call-parser auto \
  --tp 4 \
  --disaggregation-mode decode \
  --disaggregation-transfer-backend nixl \
  --disaggregation-ib-device mlx5_0 \
  --host 0.0.0.0 --port 30001

Hopper · bf16

Hopper(H200)에서 M3는 Triton MoE와 내장 Triton sparse 경로로 bf16 빌드(§2.4)를 실행하며, 두 역할이 sparse-index 전송이 의존하는 page 레이아웃을 공유하도록 --page-size 128로 고정해요. 이는 검증된 2 × 8×H200 설정(각각 TP8)이에요.

sglang serve \
  --model-path MiniMaxAI/MiniMax-M3 \
  --trust-remote-code \
  --reasoning-parser auto \
  --tool-call-parser auto \
  --tp 8 \
  --attention-backend triton \
  --moe-runner-backend triton \
  --page-size 128 \
  --disaggregation-mode prefill \
  --disaggregation-transfer-backend mooncake \
  --disaggregation-ib-device mlx5_0,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7 \
  --host 0.0.0.0 --port 30000 \
  --disaggregation-bootstrap-port 8998
sglang serve \
  --model-path MiniMaxAI/MiniMax-M3 \
  --trust-remote-code \
  --reasoning-parser auto \
  --tool-call-parser auto \
  --tp 8 \
  --attention-backend triton \
  --moe-runner-backend triton \
  --page-size 128 \
  --disaggregation-mode decode \
  --disaggregation-transfer-backend mooncake \
  --disaggregation-ib-device mlx5_0,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7 \
  --host 0.0.0.0 --port 30001

그런 다음 PD 라우터를 시작해 prefill 부트스트랩(URL + 그 --disaggregation-bootstrap-port)과 decode 엔드포인트를 가리켜요:

python3 -m sglang_router.launch_router \
  --pd-disaggregation \
  --prefill http://<prefill-host>:30000 8998 \
  --decode http://<decode-host>:30001 \
  --policy round_robin \
  --host 0.0.0.0 --port 8000

클라이언트는 라우터를 단일 서버처럼 정확히 때려요 — 각 요청을 두 스테이지에 걸쳐 투명하게 분할해요:

from openai import OpenAI

client = OpenAI(base_url="http://<router-host>:8000/v1", api_key="EMPTY")

response = client.chat.completions.create(
    model="MiniMaxAI/MiniMax-M3-MXFP8",
    messages=[{"role": "user", "content": "What is 2 + 2?"}],
    max_tokens=64,
)
print(response.choices[0].message.content)

출력 예시:

2 + 2 = 4

검증. PD 분리는 출력 품질을 보존해요 — K-only sparse 인덱스 전송이 완전히 도착하고 분리된 출력이 분리되지 않은 서빙과 일치해요. GSM8K는 위 벤치마크 카드에서 사용한 단일 sgl-eval 하네스로 채점돼요(전체 1319 문제 스플릿, --thinking 포함 채팅); 플랫폼별 단일 노드 정확도는 그 카드를 참조하세요.

  • 2 × 4×B200 (TP4+TP4, MXFP8, InfiniBand 위 NIXL) — 출력이 단일 노드 서빙과 일치. 2-노드 PD 서빙 벤치마크(512-토큰 입력, 256-토큰 출력, 동시성 16 — 카드의 단일 노드 random isl=2048 / osl=256 / conc=64 행과 다른 워크로드라 처리량 수치가 직접 비교되지 않음)는 평균 TTFT 1.1s와 TPOT 16.6ms(스트림당 약 60 tok/s, 집계 약 2.3k tok/s)를 측정했어요.
  • 2 × 8×H200 (TP8+TP8, bf16, mooncake) — 출력이 단일 노드 서빙과 일치.