상태: 실측 (2026-10-31). 24GB GPU 서버 한 대에 gpu-unlock 2.0.0 을 걸고, 걸기 전과 후를 같은 일회용 파드·같은 스크립트로 쟀습니다. 행렬곱은 dtype 마다 7회, 프로파일은 3회 반복의 중앙값이고, 지연은 600프레임 세션을 여러 번 돌린 값입니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 원인을 확인하지 못한 해석은 「추정」 으로 표시합니다. 이 글은 사용을 권하는 글이 아닙니다.
목차
- 1. 결론부터
- 2. gpu-unlock 이 하는 일
- 3. 걸기 전에 알아 둘 위험과 라이선스
- 4. 측정 환경과 방법
- 5. 걸기 전에 상한부터: 텐서 코어 비중 4.7%
- 6. 풀렸나: 행렬곱은 1.61배, 2배가 아닌 이유
- 7. 출력은 같은가: 대조군과 비트 비교
- 8. 실시간 점수판: 상한 안의 −0.9%
- 9. 끝에서 끝 지연 22.5 → 17.4ms 는 gpu-unlock 효과가 아니다
- 10. 되돌리는 법
- 11. 정리
1. 결론부터
「소비자용 GPU 의 텐서 코어는 절반 속도로 묶여 있고, 풀면 두 배가 된다」 는 말을 직접 확인해 보았습니다. 결과는 반은 맞고 반은 일의 모양에 달려 있었습니다.
- 제한은 풀렸습니다. 커널 로그에
gpu_unlock: unlocked가 찍혔고 속도 선택 레지스터 SS0 이0x43301046에서0x88888888로 바뀌었습니다. 행렬곱 8192³(FP32 누적)은 BF16 70.1 → 113.0 TFLOPS(×1.61), FP16 68.4 → 108.9, TF32 37.2 → 61.3 이 되었습니다. 텐서 코어를 쓰지 않는 FP32 는 23.8 → 23.8 로 그대로였습니다(대조군). - 2배가 아니라 1.6배인 것은 전력 때문이었습니다. 클록당 처리량은 약 1.95배로 늘었지만, 카드가 350W 상한에 붙으면서 SM 클록이 약 1710MHz 에서 1395MHz 로 내려갔습니다. 적용 후 클록은 100ms 간격으로, 적용 전 클록은 0.5초 간격 표본으로 잰 근사입니다.
- 출력은 같았습니다. 운영 실시간 모델의 출력 6종을 결정적 설정에서 200프레임씩 비교했고 sha256 이 모두 같았습니다. 다만 운영과
같은 설정(
cudnn.benchmark=True)에서는 풀린 뒤 cuDNN 이 다른 합성곱 알고리즘을 골랐으므로, 그 설정의 비트 일치는 미실측입니다. - 실시간 탁구 점수판의 공 찾기 모델은 0.9% 빨라졌습니다. 프레임당 GPU 커널 시간 9.09 → 9.00ms 입니다. 걸기 전에 프로파일로 계산해 둔 상한 −2.3% 안에 들어왔습니다. 이 모델의 GPU 시간에서 텐서 코어가 차지한 몫이 4.7% 뿐이었기 때문입니다.
- 끝에서 끝 지연 22.5 → 17.4ms 는 gpu-unlock 효과가 아닙니다. GPU 쪽과 작업자 경로는 그대로였고, 같은 부팅 안에서 다시 재니 26.2ms 와 17.2ms 가 번갈아 나왔습니다. 서버 쪽 CPU 구간의 차이이며, 그 원인은 끝내 확정하지 못했습니다(9절).
2. gpu-unlock 이 하는 일
텐서 코어는 작은 행렬 곱셈을 한 번에 처리하는 전용 연산 장치입니다. 엔비디아의 소비자용 카드는 FP16·BF16·TF32 입력을 FP32 로 누적하는 텐서 코어 연산을, 같은 칩을 쓰는 전문가용 카드의 절반 속도로 돌립니다. 학습과 양자화하지 않은 추론은 대부분 이 경로를 탑니다. FP16 으로 누적하는 연산, INT8, 텐서 코어를 쓰지 않는 FP32 는 이 제한과 관계가 없습니다.
gpu-unlock 은 이 속도를 정하는 레지스터를 부팅 때 바꾸는 리눅스 커널 모듈입니다. 저장소 README 의 「How it works」 와 「Boot flow and opt-in」 절을 요약하면 이렇습니다.
- 패키지가 모듈과 펌웨어를 initramfs 에 넣습니다. 그래서 모듈은 NVIDIA 드라이버가 카드를 잡기 전에 돕니다.
- 모듈은 커널 명령줄에
gpu_unlock.targets=가 있을 때만 일합니다.all이나 PCI 주소를 적습니다. - 모듈은 NVIDIA 드라이버 패키지에서 뽑은 서명된 부팅 펌웨어(GPU 보안 프로세서가 돌리는 booter)를 거쳐 보호된 레지스터에 쓸 권한을
얻고, 속도 선택 레지스터 SS0 을
0x88888888, SS1 을0x8로 씁니다. - 카드를 리셋(FLR)한 뒤 손을 뗍니다. 그다음 고치지 않은 NVIDIA 드라이버가 평소처럼 카드를 잡습니다.
드라이버를 고치거나 VBIOS 를 다시 쓰는 방식이 아니므로 드라이버를 올려도 다시 할 일이 없다는 것이 저장소의 설명입니다. 부팅이 끝난 뒤 손으로 모듈을 올리면 이미 드라이버가 잡은 카드에는 아무 일도 하지 않습니다.
적용한 서버의 커널 로그는 다음과 같았습니다(필요한 부분만, 주소는 줄였습니다).
gpu_unlock: loading out-of-tree module taints kernel.
gpu_unlock: module verification failed: signature and/or required key missing - tainting kernel
gpu_unlock: stage=gfw plm=0xffffff8f ss0=0x43301046 ss1=0x6 ...
gpu_unlock: pass=1/2 attempt=1 ... ret=0
gpu_unlock: pass=2/2 attempt=1 ... ret=0
gpu_unlock: stage=host ss0=0x88888888 ss1=0x8 ...
gpu_unlock: stage=flr ... ss0=0x88888888 ss1=0x8 ...
gpu_unlock: unlocked
모든 단계가 같은 초 안에 끝났습니다. 적용 뒤에도 드라이버 버전(580.95.05), 최대 클록(2100MHz), 전력 상한(350W)은 그대로였습니다.
3. 걸기 전에 알아 둘 위험과 라이선스
이 모듈은 GPU 보안 프로세서의 부팅 경로를 건드리는 서드파티 코드입니다. NVIDIA 가 만들거나 지원하는 것이 아닙니다. 아래는 저장소 문서와 이번 적용에서 직접 본 것입니다.
| 항목 | 내용 | 근거 |
|---|---|---|
| 커널 taint | 트리 밖 모듈이라 커널이 O(트리 밖) 표시를 달았고, DKMS 가 자체 키로 서명은 했지만 그 키가 등록되지 않아 검증에 실패해 E(서명 없음)도 달렸다. /proc/sys/kernel/tainted 는 12289(P·O·E 의 합). taint 된 커널의 버그 보고는 커널 개발자가 받지 않을 수 있다 |
실측(커널 로그) |
| Secure Boot | 이 서버는 Secure Boot 가 꺼져 있었고, 적용 스크립트는 켜져 있으면 멈추게 했다. 켜진 채로 쓰려면 모듈에 서명하고 그 키를 펌웨어에 등록해야 하는데, 이번에는 해 보지 않았다 | 실측(적용 스크립트), 서명 경로는 미실측 |
| 지원 범위 | Ampere 세대는 안정, Ada·Turing 세대는 실험 단계, Blackwell 은 지원하지 않는다고 적혀 있다 | 저장소 README |
| 노트북 카드 | 외장 GPU 가 쉬는 동안 전원이 완전히 꺼지면 풀림이 조용히 사라진다. 저장소도 결과가 섞인다고 적는다. 실험으로만 다루는 것이 맞다 | 저장소 README, 미실측 |
| 절전 | 메모리 절전(suspend to RAM)에서 돌아올 때 다시 푼다. 최대 절전(hibernation)은 다루지 않는다 | 저장소 README, 미실측 |
| 실패 시 | 카드의 펌웨어가 멈추지 않으면 모듈이 DMA 버퍼를 다음 전원 차단까지 붙들고 있는다. 다시 시도하려면 전원을 완전히 끈다 | 저장소 README, 미실측 |
| 화면 | 화면을 내는 카드라면 리셋부터 드라이버가 잡을 때까지 화면이 비어 있다 | 저장소 README, 이번 서버는 화면 없이 운용 |
| 발열·수명 | 저장소 FAQ 는 해가 될 가능성이 낮다고 적는다. 이번에 행렬곱을 8초씩 돌린 동안 전력은 상한(350W)을 넘지 않았고 온도는 최고 67°C 였다 | 단기 값만 실측, 장기 영향은 미실측 |
라이선스. 커널 모듈과 패키징은 GPL-2.0-only 입니다. 그런데 모듈이 쓰는 펌웨어는 NVIDIA 드라이버 패키지(610.43.03)에서 바이트 그대로
뽑은 것이고, NVIDIA 펌웨어는 NVIDIA 의 라이선스(드라이버 사용권 계약)를 따릅니다. 저장소의 firmware/NOTICE.md 는 공개 오픈 커널
모듈 소스의 라이선스가 이 펌웨어에 적용되지 않는다고, 그리고 재배포 허락을 주장하지 않으며 개인이 자기 장비에서 패키징하는 것을 일차
목표로 둔다고 밝힙니다. 그래서 빌드는 매번 download.nvidia.com 에서 드라이버 .run(344,190,507 B)과 커널 모듈 소스(26,172,192 B)를 직접
받아 그 자리에서 펌웨어를 뽑습니다. 이번 적용도 그렇게 서버 안에서만 만들었고, 만든 펌웨어나 패키지를 어디에도 나눠 주지 않았습니다.
이 글은 법률 판단이 아닙니다. 상업 환경이나 여러 장비에 배포하려면 NVIDIA 의 계약 원문을 먼저 읽어야 합니다.
재부팅이 바꾼 것은 gpu-unlock 하나가 아니었습니다. update-grub 이 몇 달 전에 고쳐 두고 반영하지 않았던 /etc/default/grub 편집까지
함께 반영해서, 커널 명령줄에서 quiet splash swapaccount=0 vt.handoff=7 이 빠지고 IOMMU 인자가 두 번 들어갔습니다. 전후 비교의 변수가
하나 더 늘어난 셈이라, 재부팅 전후의 /proc/cmdline 을 나란히 놓고 보기 전에는 몰랐습니다. 적용하기 전에 update-grub 이 무엇을 만들지
먼저 확인하는 편이 좋습니다.
4. 측정 환경과 방법
| 항목 | 값 |
|---|---|
| GPU | Ampere 세대(GA102) 24GB 소비자용 카드, 전력 상한 350W(설정 최댓값도 350W), 드라이버 580.95.05 |
| 서버 | 하이브리드(P·E 코어) 24스레드 데스크톱 CPU, Ubuntu 25.04, 커널 6.14.0-37-generic |
| gpu-unlock | 2.0.0, 커밋 7eb51bc, DKMS 패키지, 대상은 그 카드의 PCI 주소 하나 |
| 측정 파드 | 운영 점수판 작업자와 같은 이미지(PyTorch 2.9.1+cu128, cuDNN 9.10)와 같은 가중치(읽기 전용), GPU 시분할 한 조각, CPU 4·메모리 8Gi 한도, 끝나면 지워지는 일회용 |
| 부하 | 측정하는 동안 운영 점수판 세션과 일괄 작업은 0건 |
측정은 네 가지입니다. 적용 전과 후에 같은 파드에서 같은 스크립트로 돌렸습니다.
- 행렬곱.
torch.mm으로 8192³ 와 4096³ 을 dtype 마다 20회(4096 은 100회) 이어 돌리는 창 7개의 중앙값. FP16·BF16 의 줄인 정밀도 누적은 꺼서 FP32 누적만 쟀습니다. 클록과 전력은 같은 시간의nvidia-smi표본(적용 전 0.5초, 적용 후 따로 8초씩 이어 돌리며 100ms)입니다. - 커널 프로파일.
torch.profiler의 trace 에서 커널 이름으로 텐서 코어 행렬곱·합성곱(s1688gemm,xmma,tensorop)과 나머지를 나눴습니다. 공 찾기 모델 BlurBall(HRNet 계열, 1.49M 파라미터, 입력 창 하나 9×288×512)을 배치 1·2·3 으로 3회씩 돌렸습니다. - 비트 비교. 같은 200프레임을 운영 모델 넷(BlurBall, EfficientTAM 영상 인코더, 탁구대 분할 머리, 탁구대 꼭짓점 머리)에 통과시킨 출력
6종의 프레임별 sha256. 결정적 설정(
cudnn.benchmark=False,cudnn.deterministic=True,use_deterministic_algorithms,CUBLAS_WORKSPACE_CONFIG=:4096:8)이고 정밀도는 운영과 같습니다. 적용 전에 두 번 돌려 둘이 같은지부터 확인했습니다. - 끝에서 끝 지연. 운영 작업자를 파드 안에 그대로 띄우고, 브라우저처럼 30fps 로 JPEG 를 보내 「보낸 때 → 그 프레임의 답을 받은 때」 를 쟀습니다. 답을 못 받은 프레임은 3장까지만 쌓고 첫 2초는 버립니다. 네트워크는 빠져 있습니다.
5. 걸기 전에 상한부터: 텐서 코어 비중 4.7%
gpu-unlock 은 텐서 코어 커널만 빠르게 합니다. 그러니 내 모델이 얼마나 빨라질지는 「GPU 시간 중 텐서 코어 커널이 차지하는 몫」 이 위에서 막습니다. 걸기 전에 이것부터 쟀습니다.
| 공 찾기 모델(배치 1, 프레임당) | 적용 전 실측 |
|---|---|
| GPU 커널 시간 | 9.09ms(커널 427개) |
| 그중 텐서 코어 커널 | 0.42ms(TF32 행렬곱 커널 2개), 4.7% |
| 가장 큰 커널 | 배치 정규화(2.9ms), clamp, 덧셈 같은 메모리에 묶인 작은 커널 |
| 합성곱 | TF32 가 허용돼 있는데도(cudnn.allow_tf32=True 기본) cudnn.benchmark 가 FP32 winograd(텐서 코어 아님)를 골랐다 |
작은 해상도와 적은 채널(16–128)의 HRNet 은 연산이 아니라 커널 수와 메모리 왕복이 비쌉니다. 텐서 코어 커널이 정확히 절반이 된다고 쳐도 줄어드는 것은 0.21ms 이고, 이것이 상한 −2.3% 입니다. 「FP32 모델이니 TF32 텐서 코어로 돌겠지」 하고 가정했다면 틀렸을 것입니다. cuDNN 은 TF32 를 허용해도 벤치마크 결과에 따라 텐서 코어를 안 쓰는 알고리즘을 고를 수 있으므로, 가정하지 말고 커널 이름을 세야 합니다.
같은 측정으로 다른 경로도 미리 봤습니다. 모델을 BF16 autocast 로 돌리면(운영과 다른 변경이고 정확도는 평가하지 않았습니다) 커널은 7.67ms, 텐서 코어 비중은 26% 로 올라가 상한이 커집니다. 탁구대 찾기(요청 때만 도는 분할 모델)는 GPU 49ms 중 텐서 코어가 약 21ms 였지만 벽시계는 1.4초로 대부분 CPU 후처리였습니다. 둘 다 「GPU 가 빨라져도 사용자가 느끼는 시간은 거의 그대로」 라고 미리 알려 주는 숫자입니다.
6. 풀렸나: 행렬곱은 1.61배, 2배가 아닌 이유
| 8192³, FP32 누적 | 적용 전(TFLOPS) | 적용 후 | 배수 |
|---|---|---|---|
| BF16 | 70.1 | 113.0 | ×1.61 |
| FP16 | 68.4 | 108.9 | ×1.59 |
| TF32 | 37.2 | 61.3 | ×1.65 |
| FP32(텐서 코어 아님, 대조군) | 23.75 | 23.83 | ×1.00 |
4096³ 에서도 BF16 ×1.68, FP16 ×1.65, TF32 ×1.65, FP32 ×1.00 이었습니다. 저장소 README 가 모은 사용자 보고에서 같은 등급 카드는 BF16 75 → 125 TFLOPS(+65%)였으니 비슷한 자리입니다.
2배가 아닌 이유는 전력에서 보였습니다. 행렬곱을 도는 동안 nvidia-smi 의 클록 제한 이유는 거의 내내 「소프트웨어 전력 상한」 이었고,
전력은 최고 348.9W 로 350W 상한에 붙어 있었습니다. 같은 시간의 SM 클록으로 나누면 이렇습니다.
| 8192³ | 적용 전 SM 클록 | 적용 전 클록당 | 적용 후 SM 클록 | 적용 후 클록당 | 클록당 배수 |
|---|---|---|---|---|---|
| BF16 | 약 1710MHz | 41.7 TFLOPS/GHz | 1395MHz | 81.3 | ×1.95 |
| FP16 | 약 1680MHz | 41.3 | 1365MHz | 80.5 | ×1.95 |
| TF32 | 약 1815MHz | 20.7 | 1485MHz | 40.9 | ×1.98 |
| FP32 | 약 1470MHz | 16.3 | 1455MHz | 16.4 | ×1.01 |
클록당 값은 같은 구간의 TFLOPS 를 그 구간의 SM 클록 중앙값으로 나눈 것입니다. GA102 백서(문서 기준)의 클록당 최대값은 FP32 누적 FP16·BF16 이 절반 속도에서 약 41.9 TFLOPS/GHz, 전체 속도에서 그 두 배입니다. 적용 전 41.7 은 절반 속도의 99%, 적용 후 81.3 은 전체 속도의 97% 입니다. 클록당으로는 거의 정확히 두 배가 되었고, 일을 두 배로 하는 만큼 전력이 먼저 차서 클록이 18% 내려갔다고 읽힙니다. 1.95 × (1395 / 1710) ≈ 1.59 로 측정한 1.61 과 맞습니다.
다만 이것은 추정입니다. 적용 전 클록은 0.5초 간격 표본에서 dtype 구간마다 4–8개를 읽은 값이라 근사입니다. 확인하는 방법은 두 가지인데
둘 다 하지 않았습니다(미실측). 하나는 전력 상한을 올려 배수가 2에 다가가는지 보는 것인데, 이 카드는 설정할 수 있는 최댓값이 350W 라
올릴 수 없었습니다. 다른 하나는 nvidia-smi -lgc 로 SM 클록을 전력 상한 아래의 같은 값에 묶고 전후를 재는 것입니다. 이 서버는 운영
작업과 GPU 를 나눠 쓰고 있어 클록 설정을 바꾸지 않았습니다.
7. 출력은 같은가: 대조군과 비트 비교
속도가 바뀌었다는 숫자만큼 「다른 것은 바뀌지 않았다」 는 숫자도 필요합니다. 두 가지를 두었습니다.
- 대조군. 텐서 코어를 쓰지 않는 FP32 행렬곱은 23.75 → 23.83 TFLOPS(×1.00), TF32 를 끈 공 찾기 모델은 커널 9.28 → 9.30ms(+0.2%)였습니다. 제한과 관계없는 경로가 그대로라는 것은, 다른 변화(클록, 드라이버, 열)가 결과를 흔들지 않았다는 뜻이기도 합니다.
- 비트 비교. 적용 전에 같은 비교를 두 번 돌려 출력 6종이 모두 같다는 것(측정 방법 자체가 결정적이라는 것)을 먼저 확인했습니다. 그다음 적용 후 출력을 적용 전 기준과 비교했습니다.
| 출력(결정적 설정) | 비교한 수 | 다른 수 | 원시 텐서 최대 절대 오차 |
|---|---|---|---|
| 공 찾기 BlurBall | 198 | 0 | 0 |
| EfficientTAM 영상 임베딩 | 200 | 0 | |
| EfficientTAM 고해상도 특징 | 200 | 0 | |
| 탁구대 분할 저해상도 로짓 | 200 | 0 | 0 |
| 탁구대 분할 IoU | 200 | 0 | |
| 탁구대 꼭짓점 히트맵 | 200 | 0 | 0 |
이 여섯은 프레임마다 sha256 을 따로 비교한 결과입니다. 탁구대 찾기의 최종 결과(여러 모델과 후처리를 거친 사각형)는 묶음 해시 하나만 비교했고 항목별로 몇 개가 같은지 세지 못했으므로 「확인됨」 에서 뺐습니다.
한계. 이 일치는 결정적 설정에서의 이야기입니다. 운영은 cudnn.benchmark=True 로 돌고, 8절에서 보듯 풀린 뒤 cuDNN 이 일부 합성곱을
TF32 텐서 코어 커널로 바꿔 골랐습니다. 알고리즘이 바뀌면 덧셈 순서가 달라지므로 운영 설정의 출력이 비트 단위로 같을 것이라고 말할 수
없습니다. 그 비교는 하지 않았습니다(미실측). 「출력이 비트 단위로 같다」 는 문장에는 항상 어떤 설정에서였는지를 붙여야 합니다.
8. 실시간 점수판: 상한 안의 −0.9%
| 공 찾기 모델 GPU 커널(프레임당, 3회 중앙값) | 적용 전 | 적용 후 | 변화 |
|---|---|---|---|
| 운영 설정, 배치 1 | 9.087ms | 9.001ms | −0.9% |
| 운영 설정, 배치 2 | 16.24ms | 16.00ms | −1.5% |
| 운영 설정, 배치 3 | 23.28ms | 22.87ms | −1.8% |
| TF32 끔(대조군), 배치 1 | 9.284ms | 9.302ms | +0.2% |
| BF16 autocast, 배치 1(운영과 다른 변경) | 7.666ms | 7.357ms | −4.0% |
| FP16 autocast, 배치 1(운영과 다른 변경) | 7.219ms | 7.071ms | −2.0% |
배치 1 의 −0.9% 는 미리 계산한 상한 −2.3% 안입니다. 작업자 안에서 디코드부터 추론까지 한 프레임 경로도 13.04 → 12.96ms(p50)로 같은
정도였습니다. 상한까지 가지 못한 것은 이상하지 않습니다. 상한은 텐서 코어 커널이 정확히 절반이 된다는 가정이고, 실제로는 텐서 코어
커널 시간이 0.42 → 0.73ms 로 늘었습니다. 풀린 뒤 TF32 합성곱이 빨라지자 cudnn.benchmark 가 일부 합성곱을 FP32 winograd 에서
TF32 implicit GEMM 커널로 바꿔 골랐고, 그 대신 나머지 커널이 8.67 → 8.27ms 로 줄었습니다. 합은 0.09ms 줄었습니다.
텐서 코어를 더 많이 쓰는 경로일수록 이득이 큽니다. 배치가 커지면 −1.5%, −1.8% 로 늘었고, 탁구대 찾기의 flash attention 커널은 6.97 → 4.75ms(−32%), 찾기 한 번의 GPU 시간은 49.3 → 43.1ms 였습니다. 그래도 찾기 한 번의 벽시계는 1,399 → 1,412ms 로 변하지 않았습니다. GPU 가 아니라 CPU 후처리가 시간을 쓰기 때문입니다.
9. 끝에서 끝 지연 22.5 → 17.4ms 는 gpu-unlock 효과가 아니다
같은 측정에서 끝에서 끝 지연(30fps 세션 하나, p50)은 22.5ms 에서 17.4ms 로 23% 줄었습니다. 표 하나만 보면 gpu-unlock 이 지연을 5ms 줄였다고 쓰고 싶어지는 숫자입니다. 하지만 GPU 커널이 줄어든 것은 0.09ms 이고, 작업자 경로도 0.08ms 줄었을 뿐입니다. 남은 5ms 는 다른 곳에서 왔습니다. 몇 가지를 더 쟀습니다.
GPU 쪽은 그대로였습니다. 세션을 도는 동안 GPU 는 전후 모두 P2 상태, SM 1695MHz, 170W, 사용률 29–30% 였습니다. 세 세션을 동시에 돌린 경우의 지연은 26.5 → 26.3ms 로 그대로였습니다. 작업자가 보고하는 서버 안 시간(받은 때 → 답이 난 때)은 20.3 → 15.9ms 로 줄었으니 차이는 서버 쪽입니다. 단계를 재는 감싸개를 씌워 적용 후 서버 안 시간을 나눠 보니, GPU 추론이 11.7ms, 그 앞의 CPU 구간(JPEG 디코드, 탁구대 광류 따라가기, 대기)이 4.2ms 였습니다. GPU 추론은 전후가 같으므로 적용 전에는 CPU 구간이 약 8.6ms 였다는 계산이 됩니다.
같은 부팅 안에서 다시 재니 숫자가 흔들렸습니다. gpu-unlock 을 건 채로, 이미지·스크립트·설정을 바꾸지 않고 20분 안에 세 번 더 쟀습니다.
| 측정 | gpu-unlock | 끝에서 끝 p50(세션별) | 서버 안 p50 | 서버 CPU 사용(전체) | 측정 파드 CPU | 같은 서버의 CI 빌드 |
|---|---|---|---|---|---|---|
| 적용 전 | 없음 | 23.6, 22.5, 22.3 | 20.3 | 7.2% | 0.90 코어 | 없음 |
| 적용 후 | 있음 | 17.4, 19.1, 17.2 | 15.9 | 7.6% | 0.76 코어 | 1.46 코어 |
| 다시 1 | 있음 | 26.2, 26.2, 26.8 | 23.7 | 9.2% | 0.96 코어 | 0.04 코어 |
| 다시 2 | 있음 | 17.4, 17.2, 17.0 | 15.7 | 8.5% | 0.66 코어 | 0.99 코어 |
| 다시 3 | 있음 | 16.8–18.1(12세션) | 15.4–16.4 | 약 9% | 0.5–1.4 코어 | 1.0–1.4 코어 |
gpu-unlock 을 건 같은 부팅 안에서 26.2ms 와 17.2ms 가 4분 간격으로 나왔습니다. 22.5 → 17.4 는 이 흔들림 안에 있습니다. 그러니 gpu-unlock 효과가 아니라는 것은 실측으로 말할 수 있고, 원인이 무엇인지는 다음 단계의 문제입니다.
처음 세운 가설은 틀렸습니다. 재부팅 뒤 같은 서버에 있던 CI 서버와 웹 파드가 다른 서버로 옮겨 갔으므로 「CPU 경쟁이 줄어서」 라고 짐작했습니다. 그런데 적용 전 측정 시간에 그 파드들이 쓴 CPU 는 모두 합쳐 약 0.1 코어였고, 서버 전체 CPU 사용은 전후 모두 7–9% 였습니다. 오히려 느린 두 번은 서버에 다른 일이 거의 없을 때였고, 빠른 세 번은 같은 서버에서 CI 빌드가 1–1.5 코어를 쓰고 있을 때였습니다. 느릴 때는 측정 파드가 같은 일에 CPU 를 더 많이 썼습니다(0.90–0.96 코어 대 0.66–0.76 코어).
이 모양은 CPU 절전 쪽을 가리킵니다. 이 서버는 powersave 조절기에 가장 깊은 유휴 상태(C10, 빠져나오는 데 680µs)를 쓰고, 30fps 한
세션은 33ms 마다 약 16ms 일하고 나머지는 쉽니다. 서버 전체가 한가하면 코어가 깊이 잠들거나 클록이 내려가 프레임마다 깨어나는 값을
치른다는 설명입니다. 이것을 시험하려고 같은 파드 안에서 코어 하나를 계속 돌리는 프로세스(nice 19)를 켜고 끄며 번갈아 쟀지만(위 표의
「다시 3」), 그때는 CI 빌드가 돌고 있어서 켜든 끄든 17ms 대였습니다. 서버가 한가해지면 다시 재려고 30분을 기다렸지만 빌드가 이어서 들어와 그런 때가 오지 않았습니다. 느린 상태를 재현하지 못했으므로 이 설명은 추정으로 남깁니다.
확인하려면 서버가 한가한 때를 골라 같은 번갈아 재기를 하거나, 코어 고정과 C 상태 제한을 걸고 재야 합니다(미실측).
이 일에서 가져갈 것은 원인 자체보다 순서입니다. 끝에서 끝 숫자가 좋아졌을 때 바로 이유를 붙이지 않고, 같은 조건으로 다시 재서 흔들림의 폭을 먼저 보고, 부분 경로(GPU 커널, 작업자 경로, 서버 안 단계)로 나눠 어디가 바뀌었는지 가르는 것입니다. 이번에는 부분 경로가 그대로였기 때문에 gpu-unlock 을 원인에서 뺄 수 있었습니다.
10. 되돌리는 법
저장소 문서와 적용 스크립트에 적어 둔 방법입니다. 이번에는 되돌려 보지 않았습니다(미실행).
| 범위 | 방법 |
|---|---|
| 한 번만 끄기 | 부팅 화면의 GRUB 편집에서 gpu_unlock.targets=... 를 지우고 부팅 |
| 계속 끄기 | /etc/default/grub 을 적용 전 백업으로 되돌리고 sudo update-grub 뒤 재부팅. 모듈은 남아 있어도 이 인자가 없으면 아무 일도 하지 않는다 |
| 완전히 지우기 | 패키지를 지운다(Debian 계열 sudo apt-get remove gpu-unlock-dkms). initramfs 가 다시 만들어진다 |
| 확인 | 이번 부팅의 커널 로그(journalctl -k -b)에 gpu_unlock: unlocked 가 없고, 행렬곱 BF16 이 다시 약 70 TFLOPS 인지 |
설정은 부팅 때 레지스터에 쓰는 값이라 카드에 남지 않습니다. 전원이 완전히 꺼지면 원래대로 돌아갑니다.
11. 정리
| 확인할 것 | 이유(이 글의 실측) |
|---|---|
| 걸기 전에 GPU 시간 중 텐서 코어 커널의 몫을 잰다 | 공 찾기 모델은 4.7% 여서 상한이 −2.3% 였고, 실제로 −0.9% 였다 |
| 커널 이름으로 센다 | TF32 를 허용해도 cuDNN 이 텐서 코어를 쓰지 않는 합성곱을 골랐다 |
| 전력 상한과 클록을 함께 본다 | 클록당 약 1.95배였지만 350W 에 붙어 클록이 18% 내려가 1.61배였다 |
| 대조군을 둔다 | FP32 행렬곱(×1.00)과 TF32 끈 모델(+0.2%)이 그대로여서 다른 변화가 끼지 않았다고 볼 수 있었다 |
| 비트 비교에는 설정을 붙인다 | 결정적 설정에서는 6종이 같았지만, 운영 설정은 풀린 뒤 알고리즘 선택이 바뀌어 미실측이다 |
| 끝에서 끝 숫자에 원인을 바로 붙이지 않는다 | 22.5 → 17.4ms 는 같은 부팅 안의 흔들림(17–26ms) 안이었고, 부분 경로는 그대로였다 |
| 재부팅이 무엇을 함께 바꾸는지 본다 | 밀려 있던 GRUB 편집이 같이 반영되어 커널 명령줄이 더 바뀌었다 |
정리하면, 이 모듈이 소비자용 카드의 FP32 누적 텐서 코어 속도를 클록당 두 배로 만든다는 것은 이 서버에서 확인되었고, 전력 상한 아래에서는 행렬곱이 1.6배 남짓이었습니다. 큰 BF16·FP16 행렬곱이 시간의 대부분인 일(언어 모델의 프롬프트 처리나 학습)이라면 이 숫자에 가깝게 빨라질 것이고, 작은 합성곱 모델처럼 메모리와 커널 수에 묶인 일이라면 거의 바뀌지 않습니다. 어느 쪽인지는 걸기 전에 프로파일 한 번으로 알 수 있습니다. 그리고 그 대가로 서명 없는 서드파티 모듈이 GPU 의 부팅 경로를 건드리고, NVIDIA 펌웨어의 라이선스 조건을 직접 읽어야 하는 부담을 집니다.
티티 한마디. 자전거 기어를 한 단 올렸더니 페달 한 바퀴에 두 배를 가요. 그런데 다리 힘(350W)은 그대로라서 페달이 천천히 돌았고, 결국 1.6배 빨라졌어요! 그리고 제 탁구공 찾기는 대부분 신발 끈 묶는 시간(메모리 왕복)이라 기어를 바꿔도 거의 그대로였답니다. 어느 날 학교에 일찍 도착했다고 기어 덕분이라고 말하면 안 돼요. 그날은 바람이 뒤에서 불었을지도 모르니까요.