주요 콘텐츠로 건너뛰기
AI 튜토리얼

두 대의 DGX Spark에서 Qwen3.8 Flash Next 176B 서빙하기

이 튜토리얼에서는 리포지토리의 단일 파일 레시피를 사용해 두 개의 NVIDIA DGX Spark 노드에서 Qwen3.8-Flash-Next-NVFP4를 다운로드하고 패치한 후 서빙하는 방법을 설명합니다. GB10 통합 메모리 제한과 모델의 SM121 희소 어텐션 요구 사항을 준수합니다.

두 대의 DGX Spark에서 Qwen3.8 Flash Next 176B 서빙하기

개요

이 프로젝트는 RadixArk/Qwen3.8-Flash-Next-NVFP4를 SGLang으로 서비스하기 위한 프로덕션 지향 배포 레시피입니다. 이 모델은 약 1,760억 개의 파라미터와 135 GB의 용량을 가진 NVFP4 양자화 혼합 전문가 모델입니다.

제공되는 start.sh 스크립트는 TP=2 텐서 병렬 처리를 사용해 두 대의 NVIDIA DGX Spark 시스템에 모델을 분산합니다. 노드는 직접 연결된 ConnectX-7 200 Gb RoCEv2 링크를 통해 통신합니다. 실행이 완료되면 헤드 노드가 0.0.0.0:8888에서 OpenAI 호환 API를 제공합니다.

이 레시피는 단순한 실행 도구 이상입니다. 가중치를 다운로드하고 검증하며, 워커에 동기화하고, 두 노드에 패치된 컨테이너 이미지를 빌드한 뒤 클러스터를 시작하고 API가 준비될 때까지 기다립니다. 중단된 다운로드를 재개하고 캐시를 재사용하며 오래된 컨테이너를 교체할 수 있도록 멱등적으로 설계되었습니다.

레시피가 제공하는 기능

  • 2노드 텐서 병렬 서비스: 한 대의 DGX Spark가 랭크 0과 SGLang 라우터를 실행하고, 두 번째 시스템이 랭크 1을 실행합니다.
  • OpenAI 호환 엔드포인트: API는 채팅, 완성, 추론 출력, 도구 호출 파싱, 텍스트 및 이미지 입력을 지원합니다.
  • 긴 컨텍스트: 기본 컨텍스트 길이는 900000토큰이며, 모델의 기본 262,144토큰 컨텍스트에 YaRN 스케일링을 적용합니다.
  • NEXTN 추측 디코딩: 기본 추측 설정은 3/1/4이며 두 노드 모두에서 CUDA 그래프 디코딩을 사용합니다.
  • SM121 호환성: 이 빌드는 DGX Spark에서 컴파일에 실패하는 Qwen Sparse Attention 경로에 Triton 폴백을 적용합니다.
  • 운영 명령: 스크립트에는 사전 점검, 다운로드, 실행, 중지, 상태 확인, 로그 확인, 스모크 테스트가 포함되어 있습니다.

README에 따르면 테스트한 2노드 클러스터에서 단일 디코딩 스트림은 초당 64.4토큰, 2개 스트림의 총 처리량은 초당 116.8토큰입니다. 4개 스트림에서는 총 처리량이 초당 114.1토큰이었습니다.

SM121 커널 패치가 필요한 이유

Qwen4Exp 아키텍처는 Qwen Sparse Attention 백엔드를 통해 어텐션을 라우팅합니다. 이 플래시 어텐션 확인자는 기존 FA2를 우선 사용하고, 그렇지 않으면 flash-attn-4 CuTe DSL 인터페이스를 사용합니다. DGX Spark의 SM121 아키텍처에서는 이 폴백이 MLIR 레이아웃 일치 오류와 함께 컴파일 단계에서 실패합니다.

이 저장소는 SM121 호환 Triton 어텐션 폴백이 포함된 파생 SGLang 이미지를 빌드해 이 부팅 차단 문제를 해결합니다.

생성되는 .patch/ 빌드 컨텍스트에는 모델의 QSA 호출 계약에 맞춘 FlashDecoding 스타일의 가변 길이 커널인 qsa_fa_fallback.py가 포함됩니다. 이 커널은 그룹화된 쿼리 어텐션, 최대 256의 헤드 차원, 온라인 소프트맥스를 지원합니다. 또한 장치에서 cu_seqlens를 읽으므로 백엔드가 시퀀스 테이블을 다시 작성하더라도 CUDA 그래프 재생이 유효하게 유지됩니다.

Docker 빌드 중 레시피는 qwen_sparse_attn_backend.py를 패치해 is_sm100_supported()가 false일 때마다 Triton 폴백이 선택되도록 합니다. 따라서 지원되는 B100 및 B200 데이터센터 GPU에서는 표준 경로가 그대로 유지됩니다.

필수 하드웨어 및 소프트웨어

클러스터 하드웨어

  • GB10 GPU, SM121 아키텍처, 각 128 GB의 통합 메모리를 갖춘 NVIDIA DGX Spark 시스템 2대.
  • 스위치 없이 두 시스템을 직접 연결한 ConnectX-7 포트.
  • 양쪽 노드에서 활성화된 관련 RoCE 인터페이스. 예를 들어 한 노드의 rocep1s0f1/enp1s0f1np1와 다른 노드의 rocep1s0f0/enp1s0f0np0입니다.
  • 약 135 GB의 체크포인트, 컨테이너 이미지, 캐시, 임시 빌드 데이터를 저장하기에 충분한 저장 공간.

헤드 노드 소프트웨어

  • Docker.
  • Hugging Face hf CLI.
  • 체크포인트에 인증이 필요한 경우 셸 환경에 설정된 Hugging Face 토큰.
  • 헤드 노드에서 워커로 비밀번호 없이 SSH에 접속할 수 있는 권한.

다음 명령으로 헤드 노드에 Hugging Face CLI를 설치합니다.

pip install hf

기본적으로 워커는 spark2라는 SSH 별칭을 통해 지정됩니다. 워커 로그인 계정은 헤드 노드에서 start.sh를 실행하는 데 사용한 사용자 이름과 동일하게 설정됩니다. 이러한 가정은 .env에서 변경할 수 있습니다.

2노드 클러스터 구성

헤드 노드에서 저장소를 복제하고 해당 디렉터리로 이동한 다음 로컬 구성 파일을 생성합니다.

git clone <this-repo>
cd <this-repo>
cp .env.example .env

.env를 편집하고 최소한 헤드 RoCE IP, 워커 RoCE IP, SSH 대상이 올바른지 확인합니다. 기본값은 다음과 같은 사설 패브릭 주소를 사용한다고 가정합니다.

HEAD_CX7_IP=10.0.22.1
WORKER_CX7_IP=10.0.22.2
WORKER_HOST=spark2

워커에서 다른 계정을 사용하는 경우 WORKER_USER를 설정합니다. SSH 대상을 완전히 직접 지정하려면 WORKER_SSH를 user@host와 같은 값으로 설정합니다. 셸 환경 변수는 .env의 값보다 우선합니다.

예상 토폴로지

클라이언트는 포트 8888의 헤드 노드 API에 연결합니다. 헤드는 랭크 0, SGLang 서버, 라우터를 실행합니다. 랭크 1은 워커에서 실행됩니다. NCCL 트래픽은 두 시스템 사이의 직접 연결된 ConnectX-7 RoCEv2 연결을 사용하도록 고정됩니다.

이 레시피는 NCCL_NET=IB를 사용하고 InfiniBand 호환 RoCE 전송을 활성화하며, 이 설정에서는 NVLS와 CUMEM을 비활성화합니다. 또한 스테이징된 호스트 NCCL 2.30.7 빌드를 미리 로드할 수 있습니다. 이 동작은 기본적으로 USE_HOST_NCCL=1을 통해 활성화됩니다.

환경 검증

모델을 다운로드하기 전에 기본 제공 프리플라이트를 실행합니다:

./start.sh doctor

doctor 명령은 패브릭, GPU, RAM, 디스크 용량, SSH 연결, 포트 및 배포 제약 조건을 확인합니다. 대규모 다운로드와 이미지 빌드를 시작하기 전에 보고된 네트워크, 인증, 스토리지 또는 리소스 문제를 모두 해결하세요.

모델 다운로드 및 동기화

헤드 노드에 체크포인트를 다운로드하고, 검증한 다음 워커에 동기화합니다:

./start.sh download

기본값인 DOWNLOAD_MODE=rsync를 사용하면 헤드 노드가 파일을 다운로드한 후 워커에 복사합니다. 이 작업은 중단 후 재개할 수 있습니다. 각 머신이 Hugging Face에서 직접 가져오도록 하려면 다음을 설정하세요:

DOWNLOAD_MODE=direct

GB10에서 SGLang의 --load-format dummy 옵션을 사용해 이 모델을 테스트하지 마세요. 저장소의 크래시 분석에 따르면 더미 초기화 프로그램은 fp8 파라미터를 일시적으로 fp16으로 변환합니다. 모델의 51.2 GB fp8 PLE n-gram 테이블로 인해 일시적인 메모리 요구량이 150 GB를 초과할 수 있으며, 사용 가능한 통합 DRAM이 약 121.7 GB인 시스템이 완전히 멈출 수 있습니다.

패치된 이미지 빌드 및 서빙 시작

다음 명령으로 2노드 배포를 시작합니다:

./start.sh serve

이 명령은 프리플라이트를 실행하고, 가중치와 이미지가 준비되었는지 확인하며, 두 노드에서 SM121 패치 이미지를 빌드하고, TP2 클러스터를 시작한 뒤 API가 준비될 때까지 기다립니다. 생성되는 로컬 이미지의 이름은 qwen38-flashnext-dspark:local입니다.

초기 부팅에는 가중치 135 GB 로드, JIT 컴파일 및 CUDA 그래프 캡처가 포함되므로 약 10분이 걸립니다. 기본적으로 스크립트는 최대 WAIT_TIMEOUT_MIN=90분까지 기다립니다.

첫 실행을 완전히 자동화하려면 다음을 사용하세요:

./start.sh

하위 명령 없이 실행하면 스크립트가 doctor, download, serve를 순서대로 실행합니다.

기본 채팅 요청 보내기

서버가 준비되면 OpenAI 호환 채팅 엔드포인트를 호출합니다:

curl http://127.0.0.1:8888/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "Qwen3.8-Flash-Next-NVFP4",
    "messages": [
      {
        "role": "user",
        "content": "Explain RoCEv2 in one paragraph."
      }
    ]
  }'

추론은 기본적으로 활성화되어 있으며 추론 콘텐츠는 별도로 스트리밍됩니다. 한 번의 요청에서 사고 기능을 비활성화하려면 JSON 본문에 chat_template_kwargs를 추가하세요:

{
  "model": "Qwen3.8-Flash-Next-NVFP4",
  "messages": [
    {"role": "user", "content": "Summarize tensor parallelism."}
  ],
  "chat_template_kwargs": {
    "enable_thinking": false
  }
}

표준 OpenAI 호환 image_url 콘텐츠 파트를 사용해 이미지를 입력할 수 있습니다. README에 따르면 텍스트와 이미지 입력을 함께 사용하는 기능은 작동하지만, 완전한 이미지 요청 예시는 제공하지 않습니다.

OpenAI 호환 클라이언트 연결

사용자 지정 OpenAI 엔드포인트를 지원하는 애플리케이션에서는 다음 기본 URL을 사용할 수 있습니다:

http://127.0.0.1:8888/v1

모델 식별자는 다음과 같습니다:

Qwen3.8-Flash-Next-NVFP4

로컬 엔드포인트가 요청을 인증하지 않더라도 클라이언트에 API 키 필드가 필요한 경우, 저장소의 예시에서는 dummy를 사용하고 인증 헤더를 비활성화합니다. 관련 모델 기능 및 호환성 설정은 다음과 같습니다:

  • 추론 활성화.
  • 텍스트 및 이미지 입력.
  • 900000 토큰 컨텍스트 윈도우.
  • 샘플 클라이언트 설정에서 출력 토큰 최대 32768개.
  • 출력 토큰 필드로 max_tokens 사용.
  • 샘플 호환성 설정에서 developer 역할 미지원.
  • 추론 노력 매개변수 미지원.

서버 운영

단일 런처는 다음과 같은 주요 관리 명령을 제공합니다:

  • ./start.sh serve는 프리플라이트 검사를 수행하고, 이미지와 가중치가 있는지 확인하며, TP2를 시작하고 준비될 때까지 기다립니다.
  • ./start.sh download는 서버를 실행하지 않고 가중치를 다운로드, 검증 및 동기화합니다.
  • ./start.sh stop은 두 컨테이너를 모두 제거하고 로그 팔로워를 종료합니다.
  • ./start.sh status는 두 노드의 컨테이너 상태를 보고합니다.
  • ./start.sh logs [N]은 헤드 및 워커 로그에서 마지막 N개 줄을 표시합니다.
  • ./start.sh smoke는 실행 중인 API에 빠른 탐욕적 완성 요청을 보냅니다.
  • ./start.sh doctor는 전체 환경 및 레시피 프리플라이트를 다시 실행합니다.

시작 문제를 진단할 때는 .serve.log, .sglang.log, .sglang-worker.log를 확인하세요. 첫 번째 파일에는 런처 출력이 기록되고, 나머지 두 파일에는 헤드 및 워커 컨테이너 로그가 포함됩니다.

GB10 메모리 예산 이해

각 DGX Spark는 CUDA 할당, 고정 호스트 메모리, Docker 및 운영 체제가 공유하는 통합 물리 메모리 풀을 제공합니다. 따라서 이 레시피는 호스트 메모리와 GPU 메모리를 서로 독립적인 리소스로 취급하지 않고 보수적인 정적 메모리 비율을 사용합니다.

보고된 노드당 메모리 구성에는 NVFP4 전문가, dense, MTP 및 비전 가중치에 약 62.5 GB, fp8 PLE 테이블용 고정 호스트 메모리 약 11 GB, 약 956,800개 토큰을 저장하는 9 GB bf16 KV 캐시, Mamba/GDN 상태용 약 3.6 GB, CUDA 그래프와 NCCL 또는 cuBLAS 워크스페이스용 약 8 GB가 포함됩니다.

그 결과 CUDA에서 볼 수 있는 할당량은 약 95.6GB이며, 추가 작업에 사용할 수 있는 공간은 약 17.5GB입니다. 고정된 PLE 테이블은 nvidia-smi에서 프로세스별 디바이스 할당으로 표시되지 않지만, 동일한 물리적 DRAM을 계속 사용합니다. 실제 여유 공간을 평가할 때는 SGLang의 available_gpu_mem 로그 값이나 free -h의 available 열을 확인하세요.

PLE 오프로딩 활성화 유지

GB10에서는 --no-ple-offload-embedding을 추가하지 마세요. SGLang의 자동 규칙은 PLE 임베딩 테이블을 CPU로 오프로딩합니다. 이를 재정의하면 GPU 측에서 랭크당 약 26GB의 고정 호스트 메모리를 사용하게 되어 시스템에 메모리 과다 할당이 발생할 수 있습니다.

일시적인 프리필 여유 공간 보존

QSA 인덱서는 크기가 chunk × history에 따라 증가하는 fp32 로짓 워크스페이스를 생성합니다. 청크 크기가 4096이고 히스토리가 300,000토큰인 경우, 관련 gather 및 top-k 복사본까지 고려하면 이 일시적 메모리에 약 8~10GB가 필요할 수 있습니다.

제공된 값은 의도적으로 보수적으로 설정되어 있습니다:

MEM_FRACTION_STATIC=0.82
CHUNKED_PREFILL_SIZE=1024
MAMBA_FULL_MEMORY_RATIO=0.3

사용 가능한 통합 메모리 예산을 기준으로 최대 일시적 메모리를 프로파일링하지 않았다면, 900,000토큰 컨텍스트에서 CHUNKED_PREFILL_SIZE를 1024보다 높이지 마세요. 저장소에는 청크 크기 4096과 MEM_FRACTION_STATIC=0.85를 함께 사용했을 때 시스템이 멈추기에 충분했다는 보고가 있습니다.

할당자 실험 피하기

이 레시피는 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True 설정을 명시적으로 권장하지 않습니다. 이 설정은 해당 배포 환경에서 검증되지 않았으며, 작성자가 사용한 검증된 DGX Spark 레시피에도 포함되어 있지 않았습니다.

중요한 튜너블 조정

.env에서 튜너블을 설정하거나 셸에서 환경 변수로 내보내세요. 셸 값이 우선합니다. 조정이 필요할 가능성이 가장 높은 설정은 다음과 같습니다:

  • PORT: 기본값은 8888이며 API를 모든 인터페이스에 바인딩합니다.
  • CONTEXT_LENGTH: 기본값은 900000입니다.
  • MAX_RUNNING_REQUESTS: 기본값은 16이지만, 기본 비율에서 Mamba 구성은 실제 동시성을 14개 슬롯으로 제한합니다.
  • SPEC_STEPS, SPEC_TOPK, SPEC_DRAFT: NEXTN 추측 디코딩에서 기본값은 각각 3, 1, 4입니다.
  • KERNEL_PATCH: 기본값은 1이며 SM121 폴백 이미지가 빌드되고 사용됩니다.
  • IMAGE: 지정된 이미지를 직접 사용하며 패치를 건너뜁니다.
  • CPUSET: GB10의 더 큰 CPU 코어에 맞춰 기본값은 5-9,15-19입니다. 고정을 비활성화하려면 빈 값으로 설정하세요.
  • EXTRA_ARGS: 추가 SGLang 인수를 마지막에 추가하므로 마지막 값이 우선하는 재정의가 가능합니다.

추론 및 도구 호출 루프 처리

기본 추론 기능을 OpenAI 도구 및 qwen3_coder 도구 호출 파서와 함께 사용하는 세션에는 알려진 SGLang 문제가 영향을 줄 수 있습니다. 서버가 토큰 ID 0을 반복해서 출력할 수 있으며, 이 토크나이저에서는 이를 !로 표시합니다. 이 현상은 max_tokens에 도달할 때까지 계속될 수 있습니다.

권장되는 우회 방법은 도구를 전송하는 요청에서만 추론 기능을 비활성화하는 것입니다:

"chat_template_kwargs": {"enable_thinking": false}

도구 파서를 사용하는 동안 서버 전체에서 추론 기능을 비활성화하려면 배포를 중지한 후 다음 재정의 옵션으로 다시 시작하세요:

./start.sh stop
EXTRA_ARGS='--tool-call-parser qwen3_coder --default-chat-template-kwargs {"enable_thinking":false}' ./start.sh serve

EXTRA_ARGS가 이미 .env에 구성되어 있다면 기존 설정에 옵션을 병합하세요. 도구를 제공하는 동일한 요청에서 추론 기능을 다시 활성화하지 마세요. 파서가 없으면 추론은 작동할 수 있지만, 도구 호출이 구조화된 message.tool_calls 대신 일반 콘텐츠에 <tool_call> XML로 표시됩니다. README에서는 이 우회 방법을 사용할 때 에이전트 temperature를 0.7 이하로 유지할 것도 안내합니다.

고급 안정성 및 성능 팁

처리량을 위해 추측 디코딩 사용

NEXTN 추측 디코딩은 측정된 설정에서 처리량을 높이는 주요 요인입니다. 기본 3/1/4 체인에서는 각 디코드 단계가 한 번의 순전파로 초안 토큰 4개를 검증합니다. 테스트된 클러스터는 두 스트림에서 총 초당 약 117토큰에 근접한 최고 처리량을 보였으며, 이후 더 많은 스트림이 남은 메모리를 두고 경쟁하면서 정체되었습니다.

필요한 경우 결정성 선택

기본 구성의 탐욕적 디코딩은 비트 단위로 재현되지 않습니다. 부동소수점 축소의 변동으로 인해 근소하게 동률인 토큰이 실행마다 달라질 수 있으며, 이는 SM121의 NVFP4 MoE GEMM에서 발생할 가능성이 높습니다. 더 느리지만 결정적인 동작을 사용하려면 다음과 같이 다시 시작하세요:

EXTRA_ARGS="--moe-runner-backend triton" ./start.sh serve

TileLang 경고를 올바르게 해석

JIT 컴파일 중 TileLang은 Logits(bx, position)이 여러 스레드에 의해 기록된다고 보고할 수 있습니다. 저장소에서는 계산된 위치가 서로 겹치지 않기 때문에 이를 오탐으로 분류합니다. 다만 group이 런타임 값일 때 검사기가 이 속성을 증명할 수 없습니다. 필요한 경우 PassKey.TL_DISABLE_DATA_RACE_CHECK로 경고를 억제할 수 있습니다.

외부 접근 보호

API는 0.0.0.0에 바인딩되므로 허용된 모든 인터페이스를 통해 접근할 수 있습니다. README는 LAN 또는 Tailscale 클라이언트에서의 접근을 설명하지만 인증 계층은 추가하지 않습니다. 엔드포인트가 일반에 노출되어서는 안 되는 경우 적절한 호스트 방화벽, 사설 네트워크 또는 프록시 제어를 적용하세요.

결론

이 저장소는 까다로운 2노드 배포를 반복 가능한 워크플로로 전환합니다. 주요 기여 사항은 SM121과 호환되는 Qwen 희소 어텐션 폴백, 세심하게 제한한 GB10 통합 메모리 설정, 직접 RoCEv2 텐서 병렬 처리, 단일 멱등성 운영 스크립트를 결합한 것입니다.

제공된 기본값으로 시작하고, 배포 전에 ./start.sh doctor를 실행하세요. 더미 가중치, 비활성화된 PLE 오프로딩, 과도하게 큰 프리필 청크, 실험적 할당자 설정은 피해야 합니다. 클러스터가 준비되면 애플리케이션은 익숙한 OpenAI 호환 채팅 및 완성 인터페이스를 통해 로컬 서버를 사용할 수 있습니다.