잰 것만
이 탭의 GPU
borch 는 WebGPU 위에서 돈다. 즉 브라우저가 내주는 GPU 위에서 돈다. 브라우저가 안 내줄 때가 있다. 이 페이지는 그때 무엇을 하는지를 플랫폼별로 적고 — GPU 를 끝내 못 얻었을 때 borch 가 무엇을 하는지도 적는다.
먼저 배지를 읽는다
코드를 돌리는 모든 페이지가 어댑터 이름을 그대로 단다. 그 배지가 진단의 전부다:
apple / metal-3이나nvidia / blackwell같은 이름 — 진짜 GPU 다. 이 페이지는 당신과 상관없다.google / swiftshader, 또는llvmpipe·lavapipe가 들어간 이름 — GPU 의 인터페이스를 쓴 CPU 다. 돌기도 하고 골든과 값도 맞는데, 찍히는 속도는 전부 CPU 의 것이다.- 어댑터 없음 — 드라이버에 묻기도 전에 브라우저가 거절했다. 이 페이지의 나머지가 그 이야기다.
배지는 어느 장치인지를 말하고, 내 GPU 로 확인은 그 위에서 답이 맞는지를 말한다. 골든 전부를 — 진짜 PyTorch 로 굳힌 케이스 하나하나를 — 당신의 탭에서 돌리고, 개수 옆에 어댑터를 함께 찍는다. 이 페이지의 벤더 줄은 전부 여기 누군가가 가진 기계에서 나왔고, 그래서 그중 하나가 몇 달 동안 틀려 있었다.
macOS
할 일이 없다. Apple M4 Max 에서 측정 — 어댑터가 apple / metal-3 으로 오고,
플래그도 설치도 설정도 없이 Run 이 돈다. 이 사이트에서 apple / metal-3 이라
적힌 수는 전부 그렇게 잰 것이다. 스위트 전체도 그렇다 —
agreeing 4733 / 4733 [apple / metal-3].
첫 실행이 얼마나 걸리나 — 어댑터별
tests/browser/first_run.py 로 2026-09-03 에 잰 값. 랜딩 페이지를 새로 열고, 장치를
probe 하고, Run 을 눌러 done 줄까지. "페이지 안" 은 페이지 자신의 타이머, "열기→done" 은
페이지를 연 시점부터의 벽시계. 배포 행은 GitHub Pages 전송이 더해진다.
| 어댑터 | 방문 | 페이지 안 | 열기→done | 기다림의 정체 |
|---|---|---|---|---|
apple / metal-3M4 Max, Chrome | 첫 방문 | 1.35 s | 1.6 s | 어댑터는 수십 ms 에 답한다. 첫 클릭은 Python 의 첫 import(0.5 s)와 실행값을 치른다 |
| 재방문 | 0.66 s | 1.3 s | ||
| 배포 사이트, 첫 방문 | 1.71 s | 1.9 s | ||
nvidia / lovelaceRTX 4090, 드라이버 550, Ubuntu (Xorg + GNOME), Chrome 143, 모니터 켠 상태 | 첫 방문 | 1.60 s | 1.8 s | Apple 과 같다: adapter 14 ms, device 15 ms, 셰이더 22 개 1 ms, 첫 readback 28 ms. 켜진 모니터 앞에서 잰 값 — 같은 기계가 화면이 꺼진 채로는 무엇을 읽었는지는 아래 주석에. |
| 재방문 | 1.51 s | 1.6 s | ||
nvidia / blackwellRTX 5080, 드라이버 580, Ubuntu (Xorg + GNOME), Chrome 151 | 화면을 켠 상태로 다시 잴 것 | 이전 행(첫 방문 2.1 s, "1초 단위 3초 핸드셰이크")은 ssh 로 꺼진 모니터 앞에서 잰 것이고, 그것이 잰 것은 모니터였다 — 철회. | ||
첫 학습까지 — 학습자의 시계
첫 화면은 버튼을 누르는 순간 Python 으로 모델 하나를 학습한다. 이 시계는 배포된
사이트에서 페이지를 여는 순간 시작하고(다운로드가 안에 들어간다), 읽는 사람이
느끼는 순간마다 멈춘다: 버튼 밑에 Python 준비됨, 클릭 뒤 첫 loss 줄, 배운 것 줄.
tests/browser/learner_path.py 가 매일 밤 잰다.
| 어댑터 | 열고 나서 Python 준비까지 | 클릭 → 첫 loss 줄 | 클릭 → 배운 것 | 재방문, 바로 클릭 → 배운 것 | 레슨 0, 블록 셋 |
|---|---|---|---|---|---|
apple / metal-3M4 Max, Chrome | 4.4 s | 0.7 s | 0.8 s | 1.4 s | 2.4 s |
nvidia / lovelaceRTX 4090, 드라이버 550, Ubuntu, Chrome 143, 모니터 켠 상태 | 4.7 s | 0.6 s | 0.6 s | 1.6 s | 2.6 s |
꺼진 모니터는 GPU 의 모든 대기를 1초 대기로 만든다. 이 표를 처음
채웠을 때 4090 행은 Python 준비까지 8.6 s, 클릭에서 "배운 것"까지 6.5 s 였고, 그 위의
행은 어댑터 핸드셰이크가 1초 단위로 3초 걸린다고 했다. 둘 다 ssh 로, GNOME 이 5분
유휴 뒤 HDMI 모니터를 꺼 둔 데스크톱에서 잰 것이다(xset q: "Monitor is
Off"). 꺼진 화면 앞에서는 프레임 스왑이 약 1초에 타임아웃되고 GPU 의 모든 대기가
그 프레임 뒤에 줄을 선다: 어댑터 요청 3.0 s, 페이지에 한 줄 쓴 뒤의 readback 1.0 s,
loss 를 찍는 학습 루프 7초. 모니터를 깨우자 같은 프로브가 52 ms, 46 ms, 0.56 s 를
읽었다. tests/browser/readback_probe.py --fresh 가 줄마다 재현한다.
타이밍 스크립트는 이제 꺼진 화면 앞에서는 재기를 거부하고 그 사실을 말한다.
이 라이브러리 쪽에서 하는 것: 페이지가 어댑터를 한 번만 요청하고(probe 가 받은
어댑터를 init() 이 그대로 쓴다), 읽는 동안 device 와 Python 을 미리
준비해 클릭은 실행값만 치르게 한다. 화면이 켜져 있으면 NVIDIA 드라이버 위 Linux 는
여기 있는 모든 숫자에서 Apple 과 같다.
NVIDIA 카드를 단 리눅스
플래그 둘이면 충분하고, 러너도 이제 그 둘을 보냅니다. 그 둘이 없을 때 벌어지는 일은 거절이 아닙니다 — 그보다 조용합니다. 크롬은 자기 CPU 래스터라이저를 내주고 페이지를 돌게 두며, Vulkan 백엔드가 켜질 때까지 계속 그럽니다. 두 카드를 같은 방식으로 쟀습니다 — 진짜 X 세션과 시스템 크롬:
| 크롬을 이렇게 띄우면 | RTX 5080 드라이버 580 · 크롬 151 |
RTX 4090 드라이버 550 · 크롬 143 |
|---|---|---|
| 아무것도 없이 | 없음 | 없음 |
--enable-features=Vulkan 단독 |
nvidia / blackwell |
없음 |
--enable-unsafe-webgpu |
google / swiftshader |
google / swiftshader |
+ --ignore-gpu-blocklist |
google / swiftshader |
google / swiftshader |
+ --enable-features=Vulkan |
nvidia / blackwell |
nvidia / lovelace |
+ --disable-gpu-driver-bug-workarounds |
nvidia / blackwell |
nvidia / lovelace |
--enable-unsafe-webgpu --enable-features=Vulkan이 행이 러너의 목록이다 |
nvidia / blackwell |
nvidia / lovelace |
그러니 붙여넣을 줄은 이것입니다. 플래그 둘, 그리고 일을 하는 것은 둘째 — 리눅스에서 WebGPU 는 크롬의 Vulkan 백엔드 위에서 돌고, 그것이 켜지기 전까지 브라우저는 거절하는 대신 자기 CPU 래스터라이저를 대신 내줍니다. 카드가 막힌 적은 없었고, 백엔드가 꺼져 있었을 뿐입니다.
google-chrome --user-data-dir=/tmp/borch-check \
--enable-unsafe-webgpu --enable-features=Vulkan
chrome://flags 에서는 #enable-unsafe-webgpu 와
#enable-vulkan 입니다. 바꾼 뒤 다시 띄워야 합니다 —
이미 열려 있던 창은 예전 설정을 그대로 씁니다. 여기서 한 시간을 썼습니다.
그 둘로 안 되면 --ignore-gpu-blocklist 를 더하십시오.
크롬은 드라이버에 아무것도 묻기 전에 거절하는 GPU·드라이버 조합 목록을 들고 있고, 그
플래그가 그것에게 가서 보라고 말하는 방법입니다. 여기서 잰 어느 기계에서도 아무것도
바꾸지 않았습니다 — 다만 그 기계들은 하나도 그 목록에 없었습니다.
그러니 이 페이지는 그 플래그가 존재하는 이유가 되는 상황을 본 적이 없습니다.
당신 기계가 그것일 수 있습니다.
그 두 플래그로 스위트 전체를 돌렸습니다. 어댑터만 물어본 것이 아니라 —
5080 에서 agreeing 4460 / 4466 [nvidia / blackwell], 그리고 학습도 맞습니다.
작은 ResNet 30 스텝의 궤적, 층별 기울기, 배치정규화 버퍼, eval 까지 torch 와 대조해
64 values, 파이썬 바인딩으로 한 번 borch.ts 자체 API 로 한 번.
갈린 여섯은 GPU 가 아니라 그 기계에 대한 것입니다. 여섯 다
device='cuda' 일 때 무슨 일이 나는지를 묻는데, 그 답이
torch 에 CUDA 가 없는 기계에서 얼려졌습니다. 거기서는 양쪽 다 거절하니
케이스가 둘 다 멈춘다가 됩니다. 그 박스는 CUDA 빌드라 torch 가 안 멈추고,
cuda.device_count 는 골든이 0 을 기대하는 자리에서 1 이라고 답합니다.
셰이더에서 나온 값은 하나도 없습니다 — 같은 여섯이 Apple Metal 에서는
통과합니다. 얼려진 기계의 성질을 같이 얼려버린 케이스의 모양입니다. 이 종류가 앞서 둘
발견돼 고쳐졌고, 이것들이 그 가족의 나머지입니다.
4090 은 어댑터 측정만 있습니다. 세션 도중 카드가 PCIe 버스에서 빠진 뒤 돌아오지 않아 골든은 돌린 적이 없습니다.
이 표의 이전 판은 차단목록 플래그가 5080 을 연다고 말했습니다. 아닙니다 —
오늘 같은 기계에서 세 번 돌려 전부 google / swiftshader 였습니다. 그 값은
다른 브라우저(Playwright 가 들고 다니는 크로미움, 그리고 더 옛 크롬)에서
나온 것이고, 칸에 크롬 버전을 적은 이유도 그것입니다. 두 카드는 카드·드라이버·
브라우저가 한꺼번에 다릅니다. 그러니 칸 사이의 차이를 하드웨어에만 돌릴 수
없습니다. --enable-features=Vulkan 단독이 5080 에는 닿고 4090 에는 안 닿는데,
그 사이에 크롬 메이저 여덟 개가 놓여 있습니다.
WebGPU 워킹그룹의
구현 현황은
리눅스에 다른 조합을 줍니다 — --ozone-platform=x11 --use-angle=vulkan
--enable-features=Vulkan,VulkanFromANGLE — 그리고 그것도 카드에 닿습니다. 통하는
이유는 안에 --enable-features=Vulkan 이 있어서입니다.
--use-angle=vulkan 단독은 어댑터를 아예 못 주고,
--ozone-platform=x11 은 필요 없었습니다.
AMD·Intel 카드를 단 리눅스
안 쟀다. 위 차단 목록은 NVIDIA-리눅스 항목이라 같은 스위치 둘을 먼저 시도할 만하지만, 그 카드들에서는 아무것도 돌려보지 않았다. 짐작으로 적으면 이 저장소가 쓰는 쪽이 아니라 걷어내는 쪽의 문장이 된다.
윈도우
할 일이 없다. NVIDIA 카드를 단 윈도우 데스크톱에서 측정 —
플레이그라운드 배지가 nvidia / blackwell 로 왔고 플래그를 하나도
안 켜고 Run 이 돌았다. 윈도우의 크롬은 위 리눅스 절이 다루는 Vulkan 경로가
아니라 D3D12 를 쓰므로 그 사다리는 그쪽에 안 옮겨간다 — 그리고 옮겨갈 필요도 없었다.
이 칸은 누가 페이지를 열기 전까지 안 쟀다였다. 여기 있던 글이 자기 은퇴 조건을 스스로 적어뒀다 — "누군가 그 위에서 페이지를 열기 전까지 안 쟀다로 둔다. '된다고 읽었다'고 적는 페이지와 '도는 것을 봤다'고 적는 페이지는 다른 페이지". 구현 현황 페이지는 x86·x64 윈도우를 Chrome 113 부터 기본 켜짐으로 적고 있었고, 그것을 읽은 것은 본 것과 끝내 같지 않았다. 돌아온 답과 일치하며, 그건 알아둘 값어치가 있지 이 문단이 바뀐 이유는 아니다.
ARM64 윈도우는 아직 안 쟀다. 같은 페이지가 그쪽은 여전히
#enable-unsafe-webgpu 뒤로 적는다 — 그러니 "윈도우는 된다"는 아직도
어느 윈도우냐에 따라 답이 둘이고, 그중 하나만 여기서 봤다.
폰과 태블릿
iOS 에서는 돈다. iPhone 13 mini 에서 측정 — 배지에 Apple 어댑터가 왔고 실제로 돌았다. 어댑터를 얻은 것에서 그치지 않고 화면에 결과가 나왔다는 뜻이고, 폰에서는 그 둘을 따로 확인할 값어치가 있다. iOS 버전은 적어두지 않아서 여기서 주장하지 않는다.
이 한 번의 측정이 데스크톱의 한 번보다 많은 것을 말한다 — iOS 의 모든 브라우저가 WebKit 을 쓴다. 시험할 두 번째 엔진이 없다. 사파리도, 다른 브라우저처럼 보이는 것들도 밑은 같은 엔진이다.
안드로이드에서도 돈다 — 갤럭시 Z 플립 6 의 크롬에서 측정. Adreno 어댑터가 왔고, 어댑터만이 아니라 화면에 결과가 나왔다. 소프트웨어가 아니라 진짜 GPU 이고, 이 페이지에서 중요한 구분은 그것이다.
다만 기기 하나이고, 안드로이드에서는 그 점이 가장 크다. 위의 iOS 줄이 iOS 전체를 덮는 것은 거기 엔진이 하나뿐이기 때문이다. 안드로이드는 브라우저도 엔진도 여럿이고, 그 밑의 GPU 계열도 셋이다 — Adreno·Mali·Xclipse. 스냅드래곤 폰의 크롬은 그 격자의 한 칸이다. 안드로이드 버전도 적어두지 않아서 주장하지 않는다.
배지에 어댑터 이름이 붙어 있는 이유
이 프로젝트의 예전 판은 WebGPU 를 못 잡으면 WebGL 로 떨어졌고, 한동안 그 저자들이 CPU
경로에서 잰 수를 GPU 의 수로 읽었다. 다른 모양으로 또 일어났다 — 845 건을 전부 통과한
골든 실행이 *"두 번째 벤더에서 확인"* 으로 기록됐는데, 어댑터는 내내
google / swiftshader 였다. 그 문장은 제대로 측정될 때까지 문서 셋에 살아
있었다.
그러니 규칙은 "CPU 에서는 절대 안 돈다" 가 아니라 "CPU 에서 잰 수가 GPU 의 수로 읽히면 안 된다" 이다. 배지에 어댑터 이름이 있는 이유, 소프트웨어 어댑터에서 배지가 꺼지는 이유가 그것이다.
이 페이지는 여기서 하루 동안 거짓을 말했고, 정정을 지우지 않고 남긴다.
init() 이 소프트웨어 어댑터를 조용히 받는 대신 멈춘다고 적었었다. 멈춘 적이
없다 — borch-ts/src 에 그런 검사가 없었고, SwiftShader 에서 통과한 모든 골든
실행이 그 증거다. 알려주는 것은 배지이고, 라이브러리는 브라우저가 주는 것을 받는다.
일부러 CPU 에 물어보기
어느 어댑터를 받을지는 브라우저가 정하므로, 소프트웨어 쪽을 일부러 달라고 하는 길이 있다. CPU 경로를 보고 싶을 때, 또는 내 코드가 특정 장치에 기대고 있지 않은지 확인할 때 쓴다:
await init({ forceFallbackAdapter: true });
const { software } = await probe(); // 그리고 무엇을 받았는지 안다
덜떨어진 실행이 아니라 진짜 실행이다. 오늘 측정 — 골든 전체가 양쪽에서
통과한다. google / swiftshader 에서 3079 / 0,
apple / metal-3 에서 3079 / 0. 같은 코드, 같은 값, 다른 장치.
소프트웨어 실행이 아닌 것은 속도의 출처 하나다.
GPU 를 끝내 못 얻으면
그 문장 밑에 서로 다른 두 상황이 숨어 있고 답이 다르다. 브라우저가 소프트웨어 어댑터라도 내주면 위 절이 당신의 경우다 — SwiftShader 도 WebGPU 이고, 코드는 돌고, GPU 의 것이 아닌 것은 속도뿐이다. 아무것도 안 내주면 borch.ts 가 줄 것이 없고 없는 척하는 대신 그렇다고 말한다. WebGPU 를 돌아가는 길은 없다.
파이썬 쪽은 다른 구현이다. 코어가 numpy 라 어댑터 없이 돈다
(navigator.gpu 를 지운 브라우저에서 측정: loss 17.2945 → 0.000001). 그것이
있다는 걸 아는 건 값어치가 있지만, 무엇인지는 분명히 해야 한다 — 그건 GPU 에서
CPU 로 바꾸는 스위치가 아니라 언어를 바꾸는 일이다. 같은 TypeScript 코드가 거기서
도는 것이 아니다.