Laguna-M.1

Laguna-M.1

Laguna-M.1poolside가 만든 오픈 가중치 225B 파라미터 Mixture-of-Experts 모델(토큰당 23B 활성화)로, 에이전틱 코딩과 장기 지평 소프트웨어 엔지니어링 작업을 위해 설계되었어요. Apache 2.0으로 공개됩니다.

출처: 문서

본문

Deployment

Laguna-M.1 지원은 이미 SGLang `main`에 있어요 — **softplus 요소별 attention-output gating**([PR #28400](https://github.com/sgl-project/sglang/pull/28400))과 **global-attention 수정**([PR #28604](https://github.com/sgl-project/sglang/pull/28604), M.1이 full-attention `sliding_window: 0`이므로) — 아직 태그된 릴리스에는 없지만요. 아래 두 경로는 명령 패널의 **Python / Docker** 토글과 일치해요: `main`에서 설치(Python 탭)하거나, 같은 빌드를 담은 **Docker** 이미지(CUDA 13, H200 + 모든 Blackwell 커버)를 사용하세요. 모델은 Hub에 커스텀 config 코드를 실어 보내므로 `--trust-remote-code`가 필요해요(실행 명령에 포함되어 있어요). ```bash pip install -U uv uv venv --python 3.12 && source .venv/bin/activate
  # Laguna-M.1 support is on SGLang main (PRs #28400 + #28604, plus #28649 for FP8), not yet in a
  # tagged release — install from main. The serving runtime is in the base dependencies, no extra needed:
  git clone https://github.com/sgl-project/sglang.git
  cd sglang
  uv pip install --prerelease=allow -e python
  ```

  그런 다음 해당 환경에서 아래 명령 패널의 **Python** 출력을 실행하세요. **Docker** 탭이 더 간단해요 — `lmsysorg/sglang:latest`가 CUDA-13 런타임과 M.1 코드를 번들합니다.
</Tab>

<Tab title="Docker">
  ```bash
  # CUDA 13 — covers H200 + all Blackwell:
  docker pull lmsysorg/sglang:latest
  ```

  이미지 실행 방법은 [Install → Method 3: Using Docker](../../../docs/get-started/install#method-3-using-docker)를 참고하세요. 안쪽의 `sglang serve ...`를 아래 명령 생성기가 만들어 내는 것으로 대체하세요.
</Tab>

하드웨어와 양자화를 선택해 실행 명령을 생성하세요. Laguna-M.1은 셀당 단일 Balanced 레시피를 제공해요 — poolside의 권장 동작 포인트로, 일반적인 다중 사용자 서빙에 좋은 속도/처리량 트레이드오프예요. 8-GPU HGX 플랫폼(H200 / B200 / B300)은 --tp 8을, 4-GPU Grace-Blackwell 단일 노드(GB200 / GB300)는 --tp 4를 사용해요.

Playground

Playground는 검증된 매트릭스를 넘어 SGLang 기능을 실험하는 곳이에요. 위 Deploy 패널은 SGLang 팀이 승인한 조합만 생성하며, Playground는 Deploy 패널이 현재 표시 중인 셀 위에 추가 노브(파서, DP-Attention, DeepEP / EP)를 켤 수 있게 해 줘요.

1. Model Introduction

Laguna-M.1poolside의 오픈 가중치 225B 파라미터 Mixture-of-Experts 모델(토큰당 23B 활성화)로, 에이전틱 코딩과 장기 지평 소프트웨어 엔지니어링 작업을 위해 만들어졌어요. Apache 2.0으로 공개됩니다.

주요 특징:

  • 대규모 희소 MoE: 70개 레이어 transformer — 처음 3개 레이어는 밀집 SwiGLU, 나머지 67개는 256개 전문가, top-16 라우팅(+1 공유 전문가)과 auxiliary-loss-free 부하 균형을 갖춘 희소 MoE예요.
  • 출력 게이팅이 있는 Global attention: 모든 레이어에 걸친 global attention, 64 Q-heads / 8 KV-heads(head dim 128), softplus attention output gating(PR #28400 필요).
  • 긴 컨텍스트: 262,144 토큰, YaRN 포함 RoPE.
  • 에이전틱 코딩: SWE-bench Verified, SWE-bench Multilingual, SWE-Bench Pro, Terminal-Bench 2.0에서 경쟁력 있음.
  • 네이티브 추론: 도구 호출 사이에 interleaved thinking, chat_template_kwargs={"enable_thinking": ...}으로 요청마다 토글.

사용 가능한 양자화:

Quantization Hugging Face path
BF16 poolside/Laguna-M.1
FP8 poolside/Laguna-M.1-FP8
NVFP4 poolside/Laguna-M.1-NVFP4

라이선스: Apache 2.0

리소스: Hugging Face · Release blog post · Technical report · API platform.

2. Configuration Tips

  • Trust remote code (--trust-remote-code): Laguna-M.1은 Hugging Face Hub에 커스텀 모델링/config 코드를 실어 보내므로 서버가 모델을 로드하려면 이 플래그가 필요해요.
  • 장기 컨텍스트 메모리: M.1은 global-attention(sliding-window 없음)이므로 262,144 토큰 KV 캐시가 커요. 전체 컨텍스트에서 OOM이 나면 --mem-fraction-static을 낮추거나 --context-length를 제한하세요.
  • FP8: Blackwell에서는 레시피에 --fp8-gemm-backend triton이 추가돼요 — compressed-tensors 블록-FP8 가중치 스케일이 UE8M0-패킹되어 있지 않아 기본 DeepGEMM 경로가 Blackwell(sm_100)에서 쓰레기를 생성하거든요. Triton 백엔드가 올바르고(~19% 느림) PR #28662까지의 임시 해결책이에요(그 PR은 스케일을 수정하고 DeepGEMM 고속 경로를 복원). **Hopper (H200)**에서 FP8은 추가 플래그 없이 DeepGEMM을 사용하며, 재시작마다 비용을 내지 않도록 python3 -m sglang.compile_deep_gemm --model poolside/Laguna-M.1-FP8로 멀티세션 JIT를 미리 웜업하세요.
  • 파서 (poolside_v1): 에이전틱/도구 사용 배포에서는 위 Playground에서 Reasoning ParserTool Call Parser를 활성화하세요 — --reasoning-parser poolside_v1(thinking → reasoning_content)과 --tool-call-parser poolside_v1(구조화된 tool_calls)을 생성해요.
  • Thinking 기본값: thinking은 기본적으로 꺼져 있어요; 요청마다 extra_body={"chat_template_kwargs": {"enable_thinking": True}}로 옵트인하세요.
  • Served model id: 서버는 --model-path에 전달한 대로 모델을 등록하므로, 클라이언트의 model 필드가 그것과 일치해야 해요 — poolside/Laguna-M.1(BF16) 또는 양자화 셀의 poolside/Laguna-M.1-FP8 / -NVFP4. §3 예시는 BF16 id를 사용하며, 실행한 id로 바꿔 넣으세요.
  • 권장 샘플링: poolside는 thinking 활성화 상태에서 temperature=1.0, top_k=20으로 M.1을 벤치마크해요. 이것들은 요청별 샘플링 파라미터(실행 플래그 아님)예요 — 예: OpenAI 클라이언트에서 temperature=1.0, extra_body={"top_k": 20}.

3. Advanced Usage

3.1 Reasoning

--reasoning-parser poolside_v1로 실행하거나(또는 위 Playground의 Parsers 카드에서 Reasoning Parser 토글) 하세요. Reasoning은 옵트인이에요: Laguna 채팅 템플릿이 enable_thinking=True(chat_template_kwargs로 전달)에 게이트하므로 — 일반 thinking 키는 무시돼요. 그러면 thinking 트레이스는 message.content의 최종 답과 분리되어 message.reasoning_content에 들어가며, 클라이언트 측 태그 제거가 필요 없어요.

```python from openai import OpenAI

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

response = client.chat.completions.create( model="poolside/Laguna-M.1", messages=[{"role": "user", "content": "What is 15% of 240? Explain briefly."}], max_tokens=2048, extra_body={"chat_template_kwargs": {"enable_thinking": True}}, )

message = response.choices[0].message print("=============== Reasoning ===============") print(message.reasoning_content) print("=============== Answer ==================") print(message.content)

</Accordion>

<Accordion title="Example Output">
```text
=============== Reasoning ===============
Okay, so I need to find out what 15% of 240 is. Hmm, percentages can sometimes be
tricky, but let me think. I remember that "percent" means per hundred, right? So 15%
is the same as 15 per 100 or 15/100. Maybe I can convert that percentage into a decimal
first? ... 15 divided by 100 is 0.15. ... Now, to find 15% of 240, I just need to
multiply 240 by 0.15. ... 240 times 0.1 is 24 (10% of 240), and 240 times 0.05 is 12
(half of that), so 24 + 12 = 36.
[… verifies the same result several more ways: 15/100 × 240, 240 × 15 ÷ 100,
1% × 15, and the fraction 3/20 × 240 — all give 36 …]
So ... all methods are pointing to 36. I'm pretty confident that 15% of 240 is 36.
=============== Answer ==================
To find 15% of 240, convert the percentage to a decimal (0.15) and multiply by 240:
**240 × 0.15 = 36**.

**Step-by-Step Explanation:**
1. **Convert 15% to a decimal:** 15% = 15/100 = 0.15.
2. **Multiply by 240:**
   - Break it down:
     - 10% of 240 = 24 (since 240 × 0.1 = 24).
     - 5% of 240 = 12 (half of 24).
   - Add them: 24 + 12 = **36**.

**Answer:** 15% of 240 is **36**.
Laguna-M.1의 추론 트레이스는 길어요 — 모델이 답을 여러 방식으로 탐색하고 재검증해요. 어려운 문제에는 넉넉한 `max_tokens`를 주세요(추론이 정기적으로 3k 토큰을 초과함). 위 트레이스는 축약된 것이며, 모델은 전체를 생성합니다.

3.2 Tool Calling

--tool-call-parser poolside_v1로 실행하거나(또는 위 Playground의 Parsers 카드에서 Tool Call Parser 토글) 하세요. 파서는 Laguna의 <tool_call> 출력을 표준 OpenAI tool_calls 구조로 변환해요. 도구 호출은 reasoning이 꺼진 상태에서도(enable_thinking=False, 기본값) 작동해요.

```python 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="poolside/Laguna-M.1", 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}")

</Accordion>

<Accordion title="Example Output">
```text
Tool: get_weather
Args: {"location": "Beijing"}

3.3 Prefill-Decode (PD) Disaggregation

PD disaggregation은 prefill과 decode를 RDMA KV 전송 패브릭(mooncake 또는 NIXL)으로 연결된 별도 SGLang 서버에서 실행하고, 앞에 PD 라우터를 둬요. Laguna-M.1은 표준 KV 캐시를 가진 global-attention(sliding window 없음, 희소 "index" 사이드 버퍼 없음)이므로 KV 페이지가 모델 특정 플래그 없이 전송돼요 — 그냥 --disaggregation-* 노브만 있으면 돼요. 두 역할이 모델과 플래그를 공유하므로 자동으로 같은 attention 백엔드(FlashAttention-3)와 페이지 크기를 선택해 KV 레이아웃이 전송을 위해 정렬돼요.

지원/검증된 토폴로지:

  • 동일한 텐서 병렬 처리 — prefill과 decode가 같은 --tp를 실행.
  • 단일 파이프라인 스테이지 — PP = 1(기본값).
  • mooncake 또는 NIXL 전송 백엔드, RDMA / InfiniBand 위에서.
  • 2 × 8×H200(TP8 prefill + TP8 decode, BF16), 각각 한 노드, 8×400 Gb/s NDR InfiniBand 패브릭에서 검증.

prefill 서버를 실행한 다음 decode 서버를 실행하세요 — --disaggregation-mode decode이고 bootstrap 포트가 없는 같은 레시피예요. --disaggregation-ib-device를 RDMA NIC에 지정하세요.

# Prefill server (node A)
sglang serve \
  --model-path poolside/Laguna-M.1 \
  --trust-remote-code \
  --reasoning-parser poolside_v1 \
  --tool-call-parser poolside_v1 \
  --tp 8 \
  --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
# Decode server (node B)
sglang serve \
  --model-path poolside/Laguna-M.1 \
  --trust-remote-code \
  --reasoning-parser poolside_v1 \
  --tool-call-parser poolside_v1 \
  --tp 8 \
  --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

그런 다음 prefill bootstrap(URL + 해당 --disaggregation-bootstrap-port)과 decode 엔드포인트를 가리키며 PD 라우터를 시작하세요:

# PD router
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

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

```python from openai import OpenAI

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

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


**출력 예시:**

```text
2 + 2 = 4

전송 백엔드 — mooncake(권장). mooncake는 --disaggregation-ib-device를 존중하고 등록 시점에 RDMA 연결을 설정하므로 첫 요청이 이미 빠릅니다(콜드 스타트 없음). 단일 NIC 또는 전체 8개로 작동하고, 8개 NIC 모두를 사용하면 TTFT가 낮아집니다(KV 페이로드에 더 많은 총 대역폭 — 긴 컨텍스트일수록 격차가 커짐). 8×H200(임의 isl=512 / osl=256, 16 동시)에서 약 717 tok/s 출력(약 2.2k tok/s 총), 평균 TTFT 244 ms, 평균 TPOT 17.7 ms를 제공했어요. 단일 mlx5_0 NIC로는 약 697 tok/s와 TTFT 287 ms(TPOT는 동일 — decode는 compute-bound).

전송 백엔드 — NIXL(작동하되 주의 사항 두 가지).

NIXL 경로는 **`--disaggregation-ib-device`를 무시**해요 — 그 플래그는 mooncake 전용이에요. NIXL은 UCX 백엔드를 사용하며, 그 NIC는 **`UCX_NET_DEVICES`** 환경 변수로 선택돼요. **설정하세요**(예: `export UCX_NET_DEVICES=mlx5_0:1`) — 두 서버 모두에서. 설정하지 않으면 UCX가 작동하는 크로스노드 경로를 설정할 수 없고 모든 KV 전송이 300초 타임아웃(`Request … timed out … in KVPoll.WaitingForInput`)에 걸려 500을 반환할 때까지 멈춰요.

UCX_NET_DEVICES를 고정하면 NIXL은 품질과 정상 상태 속도에서 mooncake와 일치해요(약 720 tok/s, TTFT 230 ms, TPOT 17.7 ms). 한 가지 차이는 시작 후 첫 요청이 약 38초의 일회성 UCX 연결 콜드 스타트를 부담한다는 것(단일 포트든 8개 모두든 동일)이에요. 시작 후 한 번의 던져버리는 요청으로 경로를 웜업하거나, SGLANG_DISAGGREGATION_WAITING_TIMEOUT(기본 300s)을 올려 UCX가 연결되는 동안 첫 실제 요청이 떨어지지 않게 하세요.

검증. PD disaggregation은 출력 품질을 보존해요 — disaggregated 출력은 non-disaggregated 서빙과 일치하며, GSM8K(no-thinking, 라우터 통한 200-질문 부분집합)는 0.945(mooncake, 8 NIC) / 0.940(NIXL) / 0.950(mooncake, 1 NIC)을 기록했고, 모두 100% stop-rate와 0% 오류 — 단일 노드 BF16(전체 분할에서 약 0.93)과 부합해요. 로그가 분할을 확인해 줘요: prefill 노드는 Prefill batch(CUDA graph 꺼짐)를, decode 노드는 Decode batch(CUDA graph 켜짐)를 기록해요.

더 알아보기