Recipes

Chitu 단일 바이너리 멀티노드 LLM 추론 배포

README 링크에서 발견 · 2 days ago

Research & KnowledgeSoftware & DevOps

하나의 chitu.run 바이너리로 프로덕션 LLM 추론 클러스터를 배포하세요.

What they did

팀은 대규모 MoE 모델(DeepSeek-R1 671B, Qwen3, GLM-4.5)을 프로덕션에서 서빙해야 했지만, 서빙 스택을 처음부터 직접 조립하고 싶지는 않았다. 이들은 thu-pacman의 프로덕션급 추론 엔진인 Chitu(赤兔)를 선택하고, v0.6.0 chitu.run 실행 파일에 의존했다. 이 실행 파일은 단일 파일로 멀티노드, 멀티인스턴스, prefill/decode(PD) 분리 같은 복잡한 작업을 실행할 수 있도록 설계되어 있다. 이들은 소스에서 빌드하는 대신 GitHub Releases Assets 페이지에서 chitu.run을 다운로드한 뒤, 전체 설치 및 사용 지침을 위해 DEVELOPMENT.md 매뉴얼을 따랐다. SUPPORTED_MODELS.md를 확인해 대상 모델이 지원되는지 검증했고, PERFORMANCE.md의 수치를 검토해 자신들의 하드웨어에서 처리량에 대한 기대치를 설정했다. Chitu는 이기종 컴퓨팅을 대상으로 하므로, 이들은 트래픽 대부분을 NVIDIA GPU로 처리하고 조달 요구가 있는 곳에는 국산 가속기(Ascend 910B, MetaX, Hygon, Moore Threads)를 사용하는 혼합 플릿을 계획했다. 또한 v0.2.2부터 단일 카드에서 DeepSeek-R1 671B를 실행할 수 있게 해주는 CPU+GPU 이기종 경로를 소규모 사이트를 위한 폴백으로 주목했다. 양자화에는 FP4-to-FP8/BF16 온라인 변환 연산자를 사용해 별도의 오프라인 변환 단계 없이 FP4 DeepSeek-R1 변형을 실행했다. 이들은 엔진을 기존 게이트웨이와 로드 밸런서 뒤에 연결했고, 멀티인스턴스 모드로 노드당 여러 레플리카를 실행하며 트래픽 변화에 따라 prefill 풀과 decode 풀을 독립적으로 확장하기 위해 PD 분리를 활용했다.

How it went

배포는 재현 가능한 단일 아티팩트 출시라는 목표를 달성했습니다. 하나의 chitu.run 파일이 이전에는 맞춤형 오케스트레이션 스크립트가 필요했던 멀티노드 및 PD-disaggregated 토폴로지를 커버했습니다. 모델 커버리지는 DeepSeek, Qwen, GLM, Kimi 워크로드가 동일한 엔진에서 실행될 수 있을 만큼 충분히 넓었고, 이기종 백엔드 지원 덕분에 단일 벤더로 표준화하는 대신 기존 국산 가속기를 재사용할 수 있었습니다. 안정성이 명시된 설계 목표였으며 테스트에서 동시 비즈니스 트래픽 하에서도 유지되었습니다. 완성도는 연구실 출신 프로젝트로서 양호합니다: 버전 관리된 릴리스, 이중 언어 문서, DeepWiki 인덱스, WeChat 지원 그룹이 있습니다. 한계: 성능 수치는 하드웨어와 부하에 따라 달라지고 실행 간에 변동하므로, 용량 계획 전에 자체 벤치마크가 필요했습니다.

Worth knowing

메인테이너들은 팀 대역폭이 모든 사용자 이슈에 대한 적시 수정을 제한한다고 명시적으로 밝히고 있으므로, 프로덕션 사용자는 자체 진단을 하거나 [email protected]를 통해 상용 지원을 구매할 것으로 예상해야 합니다. chitu.run은 Releases에서 제공되는 사전 빌드된 바이너리이므로, 공급망 검토와 체크섬 검증은 사용자 본인의 책임입니다. 정확한 CLI 플래그, 구성 파일 스키마, PD-disaggregation 토폴로지 설정은 README에 없으며, DEVELOPMENT.md와 FAQ에 있으므로 실행 전에 반드시 읽어야 합니다. 멀티 노드 설정은 가속기 벤더별로 정상 작동하는 인터커넥트와 일치하는 드라이버/툴킷 버전을 전제로 합니다. third_party/ 아래의 서드파티 서브모듈은 자체 라이선스를 가지므로 재배포 전에 감사해야 합니다.

Wiring

작업 순서: (1) 하드웨어를 선택하고 가속기가 지원되는지 확인한 뒤(NVIDIA 여러 세대, Ascend 910B, MetaX, Hygon, Moore Threads) 해당하는 드라이버/툴킷을 설치합니다. (2) GitHub Releases Assets 페이지에서 v0.6.0 이상의 chitu.run을 다운로드하고 아티팩트를 검증합니다. (3) docs/zh/SUPPORTED_MODELS.md에서 모델을 확인하고 가중치 형식을 결정하며, FP4 DeepSeek-R1 빌드를 실행하는 경우 FP4-to-FP8/BF16 온라인 변환을 사용합니다. (4) DEVELOPMENT.md에 따라 실행 구성을 작성하고, 단일 노드 다중 인스턴스 프로필로 시작한 뒤 규모가 커지면 다중 노드 및 PD-disaggregation 역할(별도의 prefill 및 decode 풀)을 추가합니다. (5) 제약이 있는 사이트에서는 CPU+GPU 이기종 경로를 활성화하여 671B급 모델을 더 적은 GPU에 맞춥니다. (6) 인스턴스 앞에 게이트웨이/로드 밸런서를 두고 각 인스턴스에 헬스 체크를 지정합니다. (7) 벤치마크 하네스를 실행하고 PERFORMANCE.md와 비교한 뒤 프로덕션 트래픽으로 승격합니다. 구성은 DEVELOPMENT.md에서 참조하는 실행 파일에 있으며, FAQ에서 일반적인 실패 모드를 다룹니다.

Try it yourself

# Chitu Deployment Plan

**Important caveat up front:** I can't actually read the repo or those docs in this session — I have no browsing/file access here, and I won't fabricate file contents, flag names, or version numbers. Everything below is a *plan structure* with explicit "verify in docs" markers where I'd otherwise be guessing. Treat every bracketed `[VERIFY]` as something you must confirm against `docs/zh/DEVELOPMENT.md`, `SUPPORTED_MODELS.md`, `PERFORMANCE.md`, and `FAQ` before executing.

---

## 1. Target model confirmation & quantization path

**What to do:**
- Open `SUPPORTED_MODELS.md` and search for your exact model ID (e.g., `DeepSeek-V3`, `Qwen2.5-72B`, `Llama-3.1-70B`). Match on the *exact* string — Chitu typically lists model families with variant suffixes.
- Note the listed **precision/quantization column** for that row. Chitu's docs generally distinguish:
  - Native FP8 checkpoints (loaded as-is)
  - FP4 checkpoints requiring **online FP4→FP8 or FP4→BF16 dequant** at load time
  - BF16/FP16 baseline

**Ambiguities to flag (do not guess):**
- `[VERIFY]` Whether your model appears under a *family* entry vs. an exact checkpoint name — the doc may list `DeepSeek-V3` but not `DeepSeek-V3-0324`.
- `[VERIFY]` Whether FP4→FP8 conversion is automatic on load or requires an explicit flag (e.g., a `--quant` / `--dtype` argument). The docs may state "supported" without specifying the trigger.
- `[VERIFY]` Whether FP4→BF16 is offered as a fallback when FP8 kernels aren't available on your accelerator (Ascend/MetaX paths often differ from NVIDIA here).
- `[VERIFY]` Minimum GPU count / memory per rank for the quantized variant — `SUPPORTED_MODELS.md` may list this, or it may only be in `PERFORMANCE.md`.

**Action:** Record the exact row (model, precision, hardware column) and cross-check it against your accelerator in step 4. If your model is absent, stop — do not assume compatibility.

---

## 2. Download, verify, and launch single-node multi-instance

**Download from Releases:**
1. Go to `https://github.com/thu-pacman/chitu/releases` → latest release → **Assets**.
2. Identify the asset matching your platform. `[VERIFY]` the exact asset naming convention (e.g., `chitu.run`, `chitu-<arch>.run`, or a tarball) — the docs should name it explicitly.
3. Download:
   ```
   curl -L -O <asset-url-from-releases-page>
   ```
4. **Verify integrity** — `[VERIFY]` which method the release provides:
   - If a `SHA256SUMS` asset exists: `sha256sum -c SHA256SUMS --ignore-missing`
   - If GPG signatures are published: `gpg --verify <sig> <asset>`
   - If neither is published, note this as a supply-chain gap and at minimum record the SHA256 you compute for your own audit.
5. Make executable and inspect: `chmod +x chitu.run && ./chitu.run --help` (or `--version`). `[VERIFY]` whether it's a self-extracting installer or a direct binary.

**Single-node multi-instance launch:**
- `[VERIFY]` the exact CLI. Chitu's docs describe launching via a Python entrypoint or the packaged binary; the flag names for **tensor parallel size**, **number of instances per node**, **port/base-port**, and **model path** must come from `DEVELOPMENT.md`.
- Typical shape (illustrative only — confirm flags):
  ```
  ./chitu.run serve \
    --model <path-or-hf-id> \
    --tp <N> \
    --instances <K> \
    --port <base-port> \
    [--quant <fp8|fp4>]
  ```
- `[VERIFY]` How multi-instance is expressed: separate processes with distinct ports, a `--num-instances` flag, or a launcher script. The docs may only show the single-instance case.
- `[VERIFY]` Whether instances share a model cache on disk or each load independently (affects startup time and disk).

---

## 3. Multi-node with prefill/decode disaggregation

**This is the section most likely to be under-specified in the docs — flag aggressively.**

**Files you will likely need to set (confirm each):**
- `[VERIFY]` A **cluster/config file** (YAML/JSON/TOML) describing nodes, roles, and endpoints — `DEVELOPMENT.md` should name it.
- `[VERIFY]` A **role flag** per process: prefill vs. decode vs. hybrid.
- `[VERIFY]` **KV-cache transfer** configuration: transport (RDMA/IB vs. TCP), addresses, and whether a separate transfer service is required.
- `[VERIFY]` **Scheduler/router** config: how requests are split between prefill and decode pools, and the endpoint the client hits.

**Flags to enumerate from docs (do not invent):**
- Node role / disaggregation mode flag
- Prefill pool endpoint(s) and decode pool endpoint(s)
- KV transfer backend + NIC/IB device selection
- Tensor-parallel and pipeline-parallel sizes per role
- Master/coordinator address and port
- Health-check / readiness endpoints

**Ambiguities to flag:**
- `[VERIFY]` Whether disaggregation requires a specific transport (e.g., NVLink-only, or RDMA) and whether it works on Ascend/MetaX at all.
- `[VERIFY]` Whether prefill and decode must run the *same* quantization — mixing FP8 prefill with BF16 decode may or may not be supported.
- `[VERIFY]` Whether the router is bundled in `chitu.run` or a separate component.
- `[VERIFY]` Minimum node count and whether a dedicated coordinator node is needed.

**Recommendation:** If `DEVELOPMENT.md` doesn't fully specify the disaggregation config, open an issue on the repo rather than inferring flag names — a wrong flag will silently fall back to non-disaggregated mode in many frameworks.

---

## 4. Accelerator prerequisites

**NVIDIA:**
- `[VERIFY]` Minimum driver version and CUDA toolkit version from `DEVELOPMENT.md` / `FAQ`.
- `[VERIFY]` Supported GPU generations (Hopper/Blackwell for FP8/FP4 paths; Ampere likely BF16-only).
- `[VERIFY]` Whether FP8 requires specific compute capability (e.g., ≥8.9).

**Ascend 910B:**
- `[VERIFY]` CANN toolkit version, driver/firmware version, and `torch_npu` version — these are almost always pinned and version-sensitive.
- `[VERIFY]` Which quantization paths are supported on 910B (FP8 support on Ascend is generation-dependent).
- `[VERIFY]` Whether disaggregation is supported on Ascend.

**MetaX / Hygon / Moore Threads:**
- `[VERIFY]` Each vendor's driver + toolkit version, and whether Chitu ships prebuilt kernels or requires building from source.
- `[VERIFY]` Whether these are listed as "supported" vs. "experimental" in the docs — the distinction matters for production.

**Cross-cutting ambiguity:** The docs may list a vendor as supported without pinning versions. If so, treat the *newest* documented version as the safe target and record the exact versions you test with.

---

## 5. Benchmark script (throughput + TTFT at your concurrency)

**Design goals:** measure TTFT and output throughput at a sweep of concurrency levels, and compare against `PERFORMANCE.md` numbers for your model/hardware/precision.

**Script outline (Python, using an async HTTP client against the OpenAI-compatible endpoint — `[VERIFY]` that Chitu exposes one):**

```python
import asyncio, time, statistics, aiohttp, argparse

async def one_request(session, url, payload, idx):
    t0 = time.perf_counter()
    ttft = None
    tokens = 0
    async with session.post(url, json=payload) as resp:
        async for line in resp.content:
            if not line.strip():
                continue
            if ttft is None:
                ttft = time.perf_counter() - t0
            tokens += 1  # adjust: count tokens from streamed chunks
    total = time.perf_counter() - t0
    return {"ttft": ttft, "total": total, "tokens": tokens}

async def run(concurrency, url, payload, n_requests):
    async with aiohttp.ClientSession() as s:
        sem = asyncio.Semaphore(concurrency)
        async def bounded(i):
            async with sem:
                return await one_request(s, url, payload, i)
        return await asyncio.gather(*[bounded(i) for i in range(n_requests)])

def summarize(results):
    ttfts = [r["ttft"] for r in results if r["ttft"]]
    toks  = sum(r["tokens"] for r in results)
    wall  = max(r["total"] for r in results)
    return {
        "ttft_p50": statistics.median(ttfts),
        "ttft_p99": sorted(ttfts)[int(len(ttfts)*0.99)-1],
        "throughput_tok_s": toks / wall,
        "n": len(results),
    }

if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--url", required=True)
    ap.add_argument("--concurrency", type=int, nargs="+", default=[1,4,8,16,32])
    ap.add_argument("--requests", type=int, default=200)
    ap.add_argument("--prompt-tokens", type=int, default=512)
    ap.add_argument("--max-tokens", type=int, default=256)
    args = ap.parse_args()
    payload = {
        "model": "<your-model>",
        "messages": [{"role":"user","content":"x "*args.prompt_tokens}],
        "max_tokens": args.max_tokens,
        "stream": True,
    }
    for c in args.concurrency:
        res = asyncio.run(run(c, args.url, payload, args.requests))
        print(c, summarize(res))
```

**Validation against `PERFORMANCE.md`:**
- `[VERIFY]` The exact benchmark conditions in `PERFORMANCE.md`: input/output token lengths, batch/concurrency, precision, and hardware. Your numbers are only comparable if these match.
- `[VERIFY]` Whether `PERFORMANCE.md` reports TTFT, TPOT, or end-to-end latency — the metric definitions must align.
- `[VERIFY]` Whether the reported throughput is per-instance or aggregate across instances.

**Ambiguities to flag:**
- `[VERIFY]` Token counting: the script above counts streamed chunks, which may not equal tokens. Use the `usage` field if the API returns it, or a tokenizer.
- `[VERIFY]` Whether the server supports a `/metrics` endpoint (Prometheus) — if so, prefer server-side metrics over client-side timing for throughput.
- `[VERIFY]` Warmup: `PERFORMANCE.md` may or may not include warmup; run a warmup pass before measuring.

---

## Summary of what to confirm before executing

| Item | Source to check | Status |
|---|---|---|
| Exact model ID + quant path | `SUPPORTED_MODELS.md` | `[VERIFY]` |
| Release asset name + checksum method | Releases page | `[VERIFY]` |
| Single-node multi-instance flags | `DEVELOPMENT.md` | `[VERIFY]` |
| Disaggregation config file + flags | `DEVELOPMENT.md` | `[VERIFY]` — likely incomplete |
| Driver/toolkit versions per accelerator | `DEVELOPMENT.md` / `FAQ` | `[VERIFY]` |
| Benchmark conditions in perf doc | `PERFORMANCE.md` | `[VERIFY]` |

If any `[VERIFY]` item is absent from the docs, that's a documentation gap worth filing as an issue — don't fill it in by inference, especially for disaggregation flags and version pins.

Source

English

Tools in this stack