MSProbe 디버깅 가이드
MSProbe 디버깅 가이드
MSProbe는 AI 모델의 정확도 이상과 수치 오류를 진단하는 디버깅 도구예요. 모델 학습·추론 중 중간 데이터(특성 맵, 가중치, 활성화, 레이어 출력)와 컨텍스트 메타데이터(프롬프트, 텐서 dtype, 하드웨어 구성)를 캡처·모니터링하고, 시각 분석을 지원해서 정확도 저하나 수치 오류(예: NaN/Inf, 출력 드리프트, 예측 불일치)의 근본 원인을 체계적으로 추적해요.
출처: MSProbe 디버깅 가이드
기본 정보
배경 개념: MSProbe 덤핑 레벨
MSProbe는 데이터 덤핑을 위한 정확도 레벨 세 가지를 지원하고, 각각 다른 디버깅 니즈용이에요.
- L0: 모듈 레벨에서 텐서/통계를 덤프하고
construct.json을 생성해요(시각화에서 네트워크 구조 재구성용). 모델/서브모듈 핸들을 전달해야 해요. - L1: torch API 레벨에서 텐서/통계를 덤프해요. 세밀한 API 레벨 수치 검사에 적합해요.
- mix: L0 + L1을 결합해요. 그래프 재구성과 수치 비교가 모두 필요한 시나리오에 이상적이에요.
사전 조건: MSProbe 설치
pip로 MSProbe를 설치하세요.
pip install mindstudio-probe --pre
핵심 구성 파라미터
MSProbe는 커스터마이즈된 데이터 덤핑을 위해 JSON 구성 파일을 사용해요. 모든 핵심 파라미터는 아래 표에 정리했고, 기본 JSON 구성도 참고용으로 제공해요.
구성 파라미터 표
| 필드 | 설명 | 필수 |
|---|---|---|
task |
덤프 작업 유형. 일반적인 PyTorch 값은 "statistics"와 "tensor"예요. statistics 작업은 텐서 통계(평균, 분산, 최대, 최소 등)를 수집하고, tensor 작업은 임의 텐서를 캡처해요. |
예 |
dump_path |
덤프 결과가 저장될 디렉토리. 생략하면 MSProbe가 기본 경로를 사용해요. |
아니오 |
rank |
샘플링할 rank. 빈 리스트는 모든 rank를 수집해요. 단일 카드 작업에서는 이 필드를 []로 설정해야 해요. |
아니오 |
step |
샘플링할 토큰 반복. 빈 리스트는 모든 반복을 의미해요. | 아니오 |
level |
덤프 레벨 문자열("L0", "L1" 또는 "mix"). L0는 nn.Module을, L1은 torch.api를 대상으로 하고, mix는 둘 다 수집해요. |
예 |
async_dump |
비동기 덤프 활성화 여부(PyTorch statistics/tensor 작업 지원). 기본값은 false예요. |
아니오 |
scope |
덤프 범위 커스터마이즈. 도구의 네이밍 규칙을 따르는 모듈·API 이름 두 개를 제공해 범위를 고정하면, 두 이름 사이의 데이터만 덤프돼요. 빈 리스트는 모든 모듈 또는 torch API를 덤프해요. 예: "scope": ["Module.conv1.Conv2d.forward.0", "Module.fc2.Linear.forward.0"]"scope": ["Tensor.add.0.forward", "Functional.square.2.forward"]level 설정이 무엇을 제공할 수 있는지 결정해요 — level=L0면 모듈, level=L1이면 API, level=mix면 모듈 또는 API. |
아니오 |
list |
덤프 리스트 커스터마이즈, 리스트의 요소만 덤프해요. 빈 리스트는 모든 모듈 또는 torch API를 덤프해요. 옵션: •PyTorch eager 모드에서 특정 API의 전체 이름을 제공해 그 API만 덤프. 예: "list": ["Tensor.permute.1.forward", "Tensor.transpose.2.forward", "Torch.relu.3.backward"].• level=mix일 때 모듈 이름을 제공하면 그 모듈이 실행되는 동안 생성된 모든 것으로 덤프가 확장돼요. 예: "list": ["Module.module.language_model.encoder.layers.0.mlp.ParallelMlp.forward.0"].• "list": ["relu"]처럼 일부 문자열을 제공하면 이름에 그 문자열을 포함하는 모든 API를 덤프해요. level=mix에서는 이름에 문자열을 포함하는 모듈도 확장돼요. |
아니오 |
기본 구성
{
"task": "statistics",
"dump_path": "./dump_path",
"rank": [],
"step": [],
"level": "L1",
"async_dump": false,
"statistics": {
"scope": [],
"list": [],
"data_mode": [
"all"
],
"summary_mode": "statistics"
},
"tensor": {
"scope": [],
"list": [],
"data_mode": [
"all"
],
"file_format": "npy"
},
"acc_check": {
"white_list": [],
"black_list": [],
"error_data_path": "./"
}
}
출력
덤프 파일은 정의한 dump_path에 작성돼요. 보통 다음을 포함해요.
dump.json: dtype, shape, min, max, mean, L2 norm,requires_grad같은 메타데이터 기록.construct.json: 계층적 구조 설명.level이L0또는mix일 때(시각화 필수) 내용이 비어 있지 않아요.stack.json: API/Module의 호출 스택 정보 기록.dump_tensor_data:task가tensor일 때 생성되며 수집된 텐서 데이터를 저장.
자세한 내용은 덤프 디렉토리 설명을 보세요.
참고: MSProbe가 활성화되면 cuda graph가 비활성화됩니다(disable_cuda_graph=True). MSProbe는 eager 모드에서만 덤프를 지원하기 때문이에요. warmup도 비활성화됩니다(skip_server_warmup=True). 이 단계의 데이터를 덤프할 필요가 없기 때문이에요.
End-to-end 예시
MSProbe의 전체 디버깅 워크플로는 **활성화(Enable) → 데이터 수집(Collect Data) → 시각화(Visualize) → 근본 원인 분석(Analyze Root Cause)**을 따라요. 아래는 SGLang 기반 모델 추론 디버깅의 흔한 E2E 예시예요.
예시: 커스텀 구성으로 고급 디버깅
타깃 디버깅(예: 특정 rank/step의 통계 데이터만 수집, 그래프 재구성 + 수치 비교를 위한 mix 레벨 활성화)과 문제 vs. 벤치마크 비교를 통한 근본 원인 분석에 적합해요.
Step 1: 활성화
커스텀 구성 JSON 준비
msprobe-config.json 생성(rank0/1, step0/1에 대한 통계 데이터 덤프, mix 레벨):
{
"task": "statistics",
"dump_path": "./problem_dump",
"rank": [
0,
1
],
"step": [
0,
1
],
"level": "mix",
"async_dump": false,
"statistics": {
"scope": [],
"list": [],
"data_mode": [
"all"
],
"summary_mode": "statistics"
}
}
SGLang에서 커스텀 구성으로 MSProbe 활성화
SGLang 서버를 실행하고 --msprobe-dump-config로 구성 파일 경로를 지정하세요.
python3 -m sglang.launch_server \
--model-path Qwen/Qwen2.5-0.5B-Instruct \
--host 127.0.0.1 \
--port 1027 \
--msprobe-dump-config /home/msprobe-config.json
Step 2: 데이터 수집
문제 측과 벤치마크 측 덤프 데이터 수집
일반 추론 요청을 보내 모델 실행을 트리거해요(MSProbe는 요청 처리 중 자동으로 데이터를 수집해요).
curl -H "Content-type: application/json" \
-X POST \
-d '{
"model": "Qwen/Qwen2.5-0.5B-Instruct",
"messages": [
{
"role": "user",
"content": "Hello, my name is"
}
],
"max_tokens": 10
}' \
http://127.0.0.1:1027/v1/chat/completions
- 문제 측: 위 SGLang 서버(정확도/수치 문제가 있는)를 실행하고 추론 요청을 보내면, 덤프 데이터가
./problem_dump에 저장돼요. - 벤치마크 측: 같은 커스텀 구성으로 정상 SGLang 서버(문제 없는, 예: 안정 버전 프레임워크/연산자)를 실행하고 같은 추론 요청을 보내고, 덤프 디렉토리를
./bench_dump로 이름을 바꿔요.
핵심 요구사항: 문제 측과 벤치마크 측 덤프는 유효한 비교를 위해 같은 입력과 샘플링 지점(rank/step)을 사용해야 해요.
생성된 덤프 파일 확인
덤프 파일은 정의한 ./problem_dump와 ./bench_dump에 저장되고, 이후 분석에 필요한 핵심 파일을 포함해요.
dump.json: API와 모듈의 텐서 메타데이터(dtype, shape, min/max/mean, L2 norm,requires_grad등) 기록.stack.json: API와 모듈의 호출 스택 정보 기록.construct.json: 시각화에 필요한 계층적 구조 설명, 내용이 비어 있지 않아요.
Step 3: 시각화
문제 vs. 벤치마크 비교 시각화(멀티 rank)
멀티 rank 비교 시각화 파일을 생성해요(mix 레벨은 그래프 재구성용 construct.json을 생성해요).
msprobe graph_visualize -tp ./problem_dump/step0 -gp ./bench_dump/step0 -o ./graph_output
-tp: 문제 측 덤프 데이터 경로-gp: 벤치마크 측 덤프 데이터 경로-o: 시각화 파일 출력 디렉토리
오버플로우 체크(NaN/Inf 감지)가 필요하면 -oc 파라미터를 지정하세요.
msprobe graph_visualize -tp ./problem_dump/step0 -gp ./bench_dump/step0 -o ./graph_output -oc
비교 또는 빌드 작업이 끝나면 graph_output 아래에 compare_{timestamp}.vis.db 파일이 생성돼요.
덤프된 데이터는 Matplotlib이나 Excel 같은 시각화 도구로 생성된 표나 차트를 이용해 차이를 시각화·분석하는 데 쓸 수 있어요.
TensorBoard 실행
TensorBoard를 시작하세요.
tensorboard --logdir ./graph_output --bind_all --port 6006
Step 4: 근본 원인 분석
근본 원인 찾기
TensorBoard에서의 근본 원인 분석:
- 발산 노드(정확도/수치 차이가 있는)는 빨간색으로 강조돼요(진한 빨강일수록 차이가 큼).
- 발산 노드를 클릭하면 상세 텐서 데이터(입력/출력, 파라미터)와 API/모듈 호출 스택을 볼 수 있어요.
- 검색/필터 기능으로 핵심 레이어/API(예: "relu", "conv")를 빠르게 찾아요.
- UI에서 rank/step을 전환해 크로스-rank/크로스-step 발산을 확인해요.
- 특정 노드에서 NaN/Inf 값(수치 불안정의 직접 원인)이 있는지 오버플로우 체크 탭을 확인하세요.
근본 원인 검증
발산 노드(예: 비정상 텐서 값을 가진 특정 Conv 레이어나 torch API)를 찾은 뒤, 다음으로 검증해요.
- 구성 파일의
scope/list로 덤프 범위를 이 노드로 좁혀 세밀한 데이터를 수집. - 문제가 있는 레이어/API를 수정(예: 연산자 교체, dtype 조정)하고 디버깅 워크플로를 다시 실행해 문제가 해결됐는지 확인.
문제 해결
덤프 파일이 생성되지 않음
pip show mindstudio-probe로 MSProbe 설치 여부를 확인하세요. 설치돼 있으면 MSProbe 버전 정보가 출력돼요. 설치되지 않았다면pip install mindstudio-probe --pre로 설치하세요.--msprobe-dump-config파라미터가 올바른 JSON 파일 경로를 가리키는지 확인하세요.
덤프 파일이 너무 큼(과도한 데이터)
"tensor"대신task: "statistics"로 시작해 텐서 통계만 수집하세요(raw 텐서 덤프 방지).scope필드로 덤프 범위를 좁히세요(시작/끝 모듈/API 지정).list필드로 덤프 대상을 필터링하세요(특정 모듈/API 또는 하위 문자열만 덤프).- 특정
rank와step을 샘플링하세요(모든 rank/반복 덤프 방지).
TensorBoard 시각화 실패
construct.json이 비어 있지 않은지 확인하세요(level: L0또는mix필요 — L1은 그래프 파일을 생성하지 않음).-tp(문제 덤프)와-gp(벤치마크 덤프) 경로가 유효한 rank/step 서브디렉토리(예:step0/rank0)를 가리키는지 확인하세요.- MSProbe 버전이 최신인지 확인하세요(
pip install mindstudio-probe --pre --upgrade로 재설치). - TensorBoard가 설치돼 있고
--logdir파라미터가.vis.db파일이 들어 있는 디렉토리를 가리키는지 확인하세요(파일 자체가 아니라).
수치 비교에 발산이 없는데 모델 정확도가 낮음
- 덤프
step범위를 넓히세요(후기 발산을 위해 더 많은 토큰 반복 확인). task: "tensor"로 전환하세요(통계는 raw 텐서 데이터의 미묘한 수치 차이를 가릴 수 있음).- 문제 측과 벤치마크 측 덤프가 같은 입력 데이터/하드웨어 구성을 사용하는지 확인하세요(다른 입력은 무효 비교를 만듦).
- TensorBoard의
manual mapping기능을 사용하세요(자동 매핑은 커스텀 모델의 일부 노드를 놓칠 수 있음).
부록
덤프 디렉토리 설명
├── problem_dump or bench_dump
│ ├── step0
│ │ ├── rank0
│ │ │ ├── dump_tensor_data
│ │ │ │ ├── Tensor.permute.1.forward.pt
│ │ │ │ ├── Functional.linear.5.backward.output.pt # Format: {api_type}.{api_name}.{call_count}.{forward/backward}.{input/output}.{arg_index}.
│ │ │ │ │ # arg_index is the nth input or output of the API. If an input is a list, keep numbering with decimals (e.g., 1.1 is the first element of the first argument).
│ │ │ │ ├── Module.conv1.Conv2d.forward.0.input.0.pt # Format: {Module}.{module_name}.{class_name}.{forward/backward}.{call_count}.{input/output}.{arg_index}.
│ │ │ │ ├── Module.conv1.Conv2d.forward.0.parameters.bias.pt # Module parameter data: {Module}.{module_name}.{class_name}.forward.{call_count}.parameters.{parameter_name}.
│ │ │ │ └── Module.conv1.Conv2d.parameters_grad.weight.pt # Module parameter gradients: {Module}.{module_name}.{class_name}.parameters_grad.{parameter_name}. Gradients do not include call_count because the same gradient updates all invocations.
│ │ │ │ # When the `model` argument passed to dump is a List[torch.nn.Module] or Tuple[torch.nn.Module], module-level data names also include the index inside the list ({Module}.{index}.*), e.g., Module.0.conv1.Conv2d.forward.0.input.0.pt.
│ │ │ ├── dump.json
│ │ │ ├── stack.json
│ │ │ ├── dump_error_info.log
│ │ │ └── construct.json
│ │ ├── rank1
│ │ │ ├── dump_tensor_data
│ │ │ │ └── ...
│ │ │ ├── dump.json
│ │ │ ├── stack.json
│ │ │ ├── dump_error_info.log
│ │ │ └── construct.json
│ │ ├── ...
│ │ │
│ │ └── rank7
│ ├── step1
│ │ ├── ...
│ ├── step2
rank: 디바이스 ID. 각 카드는 해당rank{ID}디렉토리에 자신의 데이터를 작성해요. 분산이 아닌 시나리오에서는 디렉토리가 단순히rank로 명명돼요.dump_tensor_data: 수집된 텐서 데이터를 저장.dump.json: 각 API 또는 모듈의 forward 데이터 통계. 이름, dtype, shape, max, min, mean, L2 norm(L2 분산의 제곱근), 그리고summary_mode="md5"일 때 CRC-32를 포함해요. 자세한 내용은 dump.json 파일 설명을 보세요.dump_error_info.log: 덤프 도구가 오류를 만났을 때만 존재하며 실패 로그를 기록.stack.json: API/모듈의 호출 스택.construct.json: 계층적 구조 설명.level=L1일 때 비어 있음.
dump.json 파일 설명
L0 레벨
L0 dump.json은 모듈의 forward/backward I/O를 파라미터·파라미터 그래디언트와 함께 포함해요. PyTorch의 Conv2d를 예로 들면, 네트워크 코드는 이렇게 생겼어요.
output = self.conv2(input) # self.conv2 = torch.nn.Conv2d(64, 128, 5, padding=2, bias=True)
dump.json은 다음 엔트리를 포함해요.
Module.conv2.Conv2d.forward.0: 모듈의 forward 데이터.input_args는 위치 입력,input_kwargs는 키워드 입력,output은 forward 출력,parameters는 가중치/바이어스를 저장해요.Module.conv2.Conv2d.parameters_grad: 파라미터 그래디언트(weight와 bias).Module.conv2.Conv2d.backward.0: 모듈의 backward 데이터.input은 모듈로 흘러 들어오는 그래디언트(forward 출력의 그래디언트),output은 흘러 나가는 그래디언트(모듈 입력의 그래디언트)예요.
참고: 덤프 API에 전달된 model 파라미터가 List[torch.nn.Module] 또는 Tuple[torch.nn.Module]이면, 모듈 레벨 이름에 리스트 내 인덱스가 포함돼요({Module}.{index}.*). 예: Module.0.conv1.Conv2d.forward.0.
다음은 L0 dump.json의 전체 예시예요.
{
"task": "tensor",
"level": "L0",
"framework": "pytorch",
"dump_data_dir": "/dump/path",
"data": {
"Module.conv2.Conv2d.forward.0": {
"input_args": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
8,
16,
14,
14
],
"Max": 1.638758659362793,
"Min": 0.0,
"Mean": 0.2544615864753723,
"Norm": 70.50277709960938,
"requires_grad": true,
"data_name": "Module.conv2.Conv2d.forward.0.input.0.pt"
}
],
"input_kwargs": {},
"output": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
8,
32,
10,
10
],
"Max": 1.6815717220306396,
"Min": -1.5120246410369873,
"Mean": -0.025344856083393097,
"Norm": 149.65576171875,
"requires_grad": true,
"data_name": "Module.conv2.Conv2d.forward.0.output.0.pt"
}
],
"parameters": {
"weight": {
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
5,
5
],
"Max": 0.05992485210299492,
"Min": -0.05999220535159111,
"Mean": -0.0006165213999338448,
"Norm": 3.421217441558838,
"requires_grad": true,
"data_name": "Module.conv2.Conv2d.forward.0.parameters.weight.pt"
},
"bias": {
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32
],
"Max": 0.05744686722755432,
"Min": -0.04894155263900757,
"Mean": 0.006410328671336174,
"Norm": 0.17263513803482056,
"requires_grad": true,
"data_name": "Module.conv2.Conv2d.forward.0.parameters.bias.pt"
}
}
},
"Module.conv2.Conv2d.parameters_grad": {
"weight": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
5,
5
],
"Max": 0.018550323322415352,
"Min": -0.008627401664853096,
"Mean": 0.0006675920449197292,
"Norm": 0.26084786653518677,
"requires_grad": false,
"data_name": "Module.conv2.Conv2d.parameters_grad.weight.pt"
}
],
"bias": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32
],
"Max": 0.014914230443537235,
"Min": -0.006656786892563105,
"Mean": 0.002657240955159068,
"Norm": 0.029451673850417137,
"requires_grad": false,
"data_name": "Module.conv2.Conv2d.parameters_grad.bias.pt"
}
]
},
"Module.conv2.Conv2d.backward.0": {
"input": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
8,
32,
10,
10
],
"Max": 0.0015069986693561077,
"Min": -0.001139344065450132,
"Mean": 3.3215508210560074e-06,
"Norm": 0.020567523315548897,
"requires_grad": false,
"data_name": "Module.conv2.Conv2d.backward.0.input.0.pt"
}
],
"output": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
8,
16,
14,
14
],
"Max": 0.0007466732058674097,
"Min": -0.00044813455315306783,
"Mean": 6.814070275140693e-06,
"Norm": 0.01474067009985447,
"requires_grad": false,
"data_name": "Module.conv2.Conv2d.backward.0.output.0.pt"
}
]
}
}
}
L1 레벨
L1 dump.json은 API의 forward/backward I/O를 기록해요. PyTorch의 relu 함수를 예로 들면(output = torch.nn.functional.relu(input)), 파일은 다음을 포함해요.
Functional.relu.0.forward: API의 forward 데이터.input_args는 위치 입력,input_kwargs는 키워드 입력,output은 forward 출력을 저장해요.Functional.relu.0.backward: API의 backward 데이터.input은 forward 출력의 그래디언트,output은 forward 입력으로 흘러 돌아가는 그래디언트예요.
다음은 L1 dump.json의 전체 예시예요.
{
"task": "tensor",
"level": "L1",
"framework": "pytorch",
"dump_data_dir": "/dump/path",
"data": {
"Functional.relu.0.forward": {
"input_args": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
28,
28
],
"Max": 1.3864083290100098,
"Min": -1.3364859819412231,
"Mean": 0.03711778670549393,
"Norm": 236.20692443847656,
"requires_grad": true,
"data_name": "Functional.relu.0.forward.input.0.pt"
}
],
"input_kwargs": {},
"output": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
28,
28
],
"Max": 1.3864083290100098,
"Min": 0.0,
"Mean": 0.16849493980407715,
"Norm": 175.23345947265625,
"requires_grad": true,
"data_name": "Functional.relu.0.forward.output.0.pt"
}
]
},
"Functional.relu.0.backward": {
"input": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
28,
28
],
"Max": 0.0001815402356442064,
"Min": -0.00013352684618439525,
"Mean": 0.00011915402356442064,
"Norm": 0.007598237134516239,
"requires_grad": false,
"data_name": "Functional.relu.0.backward.input.0.pt"
}
],
"output": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
28,
28
],
"Max": 0.0001815402356442064,
"Min": -0.00012117840378778055,
"Mean": 2.0098118724831693e-08,
"Norm": 0.006532244384288788,
"requires_grad": false,
"data_name": "Functional.relu.0.backward.output.0.pt"
}
]
}
}
}
mix 레벨
mix dump.json은 L0와 L1 레벨 데이터를 모두 포함해요. 파일 포맷은 위 예시와 같아요.