borch-hub
v0.4.1
Published
borch 런타임이 바로 불러 돌릴 수 있는 모델의 배포·검증 클라이언트
Readme
borch-hub
borch 런타임이 바로 불러 돌릴 수 있는
모델의 배포·검증 클라이언트.
브라우저에서 학습한 모델이 허브에 올라가고, 다른 사람의 브라우저가 그것을 받아 돌린다. 그 왕복을 담당하는 쪽이다.
지금 여기 있는 것
매니페스트 주소 하나로 돌 준비가 된 모델까지 간다.
import { init } from "borch-ts";
import { load, verify } from "borch-hub";
await init();
const { model, manifest } = await load("https://.../manifest.json");
const badge = await verify(model, manifest, "https://.../manifest.json");무엇이 있는지부터 물을 수 있다. 오래 그러지 못했다 — 입구가 load(주소) 하나여서
쓰는 사람이 주소를 이미 알고 있어야 했고, 모델이 스물이 되고서야 그 구멍이 보였다.
import { fetchIndex, newest } from "borch-hub";
const listing = await fetchIndex("https://models.pilab.kr/index.json");
for (const model of newest(listing)) {
console.log(model.name, model.version, model.bytes, model.manifestUrl);
}목차는 나간 판을 전부 들고 있다 — 옛 주소가 죽으면 남의 페이지가 깨지기 때문이다.
그래서 그대로 늘어놓으면 같은 모델이 여러 번 보인다(지금 스물 중 열이 그렇다).
newest 가 이름마다 가장 높은 판만 남긴다.
순서가 요점이다 — 환경 판정 → 받기 → 해시 대조 → 싣기. 앞의 둘이 뒤바뀌면 45MB 를 받은 뒤에 "이 브라우저에서는 안 됩니다" 라고 말하게 되고, 그건 배지가 아니라 사과다. 해시를 싣기 전에 보는 것도 같은 이유다 — 모양이 맞는 틀린 바이트는 예외 없이 실린다.
그리고 실은 뒤에 샘플로 대 본다. 해시는 받은 바이트가 맞다는 말이지 이
브라우저에서 그 모델이 나온다는 말이 아니다. 둘이 벌어지는 것을 실측했다 —
EfficientNet-B4 는 바이트도 층도 맞는데 이 장치에서 로짓이 전부 0 이었다
(borch#122). 그때 load 는 아무
말 없이 그 모델을 건넸다.
막을 도구는 이미 있었다(verify 가 최대 절대차 1.481 로 막는다). 아무도 안 불렀을
뿐이다. 이제 load 가 부른다 — 샘플 두 개는 600KB 남짓이고, 45MB 를 받은 뒤에
치르기에 싸다. 끄려면 load(url, { verify: false }).
load 는 fetch 를 받는다. 프라이빗 모델·조직 계정·서명 URL 이 전부 그 훅 하나를
탄다. 지금 쓰는 데가 없어도 자리를 비워 둔 것은, 나중에 넣으면 이미 쓰고 있는 쪽이
깨지기 때문이다.
빈 함수로 자리만 잡아두지 않은 것은 일부러다. 코어 저장소가 여러 번 적어둔 대로 사용자 없는 표면은 케이스가 안 생기고, 케이스 없는 표면이 조용히 틀린다. 첫 모델 하나를 끝까지 통과시키면서 같이 나온다.
계약을 먼저 두는 이유는 정확히 반대다. 매니페스트는 한 번 배포되면 못 바꾼다 — 남의 페이지가 이미 그 URL 을 박아두고 그 필드를 읽고 있기 때문이다.
카탈로그는 여기 없다 — bimm 에 있다
가중치만으로는 모델이 복원되지 않는다. 매니페스트의 arch 가 가리키는 표와
createModel 은 bimm 에 있다.
borch ←→ torch
borchvision ←→ torchvision
bimm ←→ timm 아키텍처 카탈로그
borch-hub ←→ (없음) 매니페스트 · 해시 대조 · 환경 판정이 패키지가 흉내 내는 자리는 없다. torch.hub 가 가장 가깝지만 그쪽은 저장소에서
모델을 불러오는 일이고 자기 카탈로그가 없다. 여기 있는 것 — 배포된 매니페스트, 싣기
전의 해시 대조, 받기 전의 환경 판정 — 은 timm 에도 torch.hub 에도 없다. 흉내가
아니므로 흉내인 척하지 않는다.
카탈로그가 갈라져 나간 것은 이름 때문이 아니라 방향 때문이다. models/ 가
매니페스트의 BorchHubError 와 Arch 를 임포트하고 있었는데 둘 다 매니페스트 개념이다.
카탈로그가 매니페스트를 알면 모델 하나 만들려는 사람이 배포·검증 계층을 통째로 끌어온다.
지금은 의존이 한 방향이다 — 허브는 카탈로그를 알고, 카탈로그는 허브를 모른다.
매니페스트의 arch 를 카탈로그 호출로 옮기는 createModelFor 만 이쪽에 남았다.
그것은 매니페스트 모양이기 때문이다.
왜 코어와 따로 있나
세 가지 이유다.
번들 크기. 코어가 싣는 ES 모듈이 압축 전 770KB 이고, 코어 저장소의
tests/test_site.py 가 문서에 적힌 수와 실측을 대조해 낡으면 실패시킨다. 모델
구조가 늘 때마다 그 수가 오르면 안 된다. 모델을 안 쓰는 사람이 카탈로그를 받을
이유도 없다.
버전 매트릭스가 강제된다. 매니페스트에 runtime.ts: ">=0.1.0" 이라고 적으려면
실제로 여러 borch 버전에서 돌려봐야 한다. 한 저장소 안에 있으면 "지금 트리에서
되니까 됨"으로 끝나는데, 갈라져 있으면 CI 가 그 확인을 하게 된다. 호환성 표시가
주장이 아니라 실측이 되는 유일한 길이다.
제품이 다르다. borch 는 읽히는 교육용 런타임이고, 이쪽은 모델 유통이다.
릴리스 주기도 사용자도 같지 않다.
borch 와 bimm 은 peerDependency 다
이 패키지가 borch 를 끌고 오면 사용자의 것과 두 벌이 된다. 텐서 두 벌은 같은
GPU 장치를 공유하지 않아서 예외 없이 조용히 안 맞는다. 쓰는 쪽이 이미 깔아둔
것을 쓴다.
bimm 도 같은 이유다 — 그쪽 층이 사용자의 borch 위에 서므로, bimm 이 두 벌이면
borch 도 두 벌이 된다.
하한이 낮은 것은 실수가 아니다
bimm-ts >=0.2.1 이다. 0.2.1 의 카탈로그에는 resnet 과 mobilenet 뿐이라, 지금
나가 있는 모델 열 중 일곱은 그 판으로 못 만든다 — efficientnet 과 mobilenetv3 는
0.4.0, vit 는 0.6.0 에 생겼다.
그래도 하한을 올리지 않는다. 낡은 카탈로그로도 그것이 아는 모델은 돌기 때문이다. 올리면 resnet 하나 쓰려는 사람까지 같이 막힌다.
대신 checkEnvironment 가 받기 전에 카탈로그에 그 이름이 있는지 묻는다. 없으면
그 자리에서 이름과 지금 있는 것을 같이 말하고 멈춘다 — 45MB 를 쓴 뒤가 아니다.
매니페스트에 bimm 하한을 적는 길도 있었다. 그러려면 카탈로그가 자기 판을 내보내야
하고, 스키마가 바뀌어야 하고, 나가 있는 매니페스트를 다시 올려야 한다. 그러고도
카탈로그에 새 이름이 생길 때마다 그 숫자가 낡는다. 있는지 없는지는 카탈로그가
이미 안다.
매니페스트 스펙의 정본은 여기가 아니다
정본은 borch-hub-registry/schema/manifest.schema.json
이고, src/manifest.ts 는 그것을 TypeScript 로 옮긴 거울이다. 거울은 원본과 갈릴
수 있다 — 갈리면 레지스트리는 통과시키는데 클라이언트가 거절하는 매니페스트가
생기고, 그건 올린 사람이 아니라 받는 사람이 만난다.
그래서 두 가지를 따로 묻는다.
test/schema.test.ts 는 저장소에 든 스키마 사본에서 주장을 뽑아 거울에게 물어본다 —
허용하는 값을 전부 넣어 보고, 목록에 없는 값도 하나 넣어 보고, 필수 필드를 하나씩
빼 본다. 글자를 대 보지 않는 것은 JSON Schema 와 손으로 쓴 파서가 모양이 달라서다.
npm run schema:fresh 는 그 사본이 정본과 같은지 묻는다. 스키마의 $id 가 자기
주소를 담고 있어서 여기에 주소를 또 적지 않는다. 네트워크가 필요한 유일한 단계라
CI 에서도 따로 서 있다 — 사본이 낡은 것과 거울이 갈린 것은 다른 일이고, 한 칸에
넣으면 그 구별이 사라진다. 못 받으면 건너뛰지 않고 빨개진다.
돌리기
npm install
npm test # 배포되는 코드 전부
npm run check # 타입만코어와 카탈로그는 npm 에서 온다 — borch-ts·bimm-ts. 접미사가 붙은 것은 갈려서가
아니라 레지스트리의 유사성 필터가 borch 도 bimm 도 막기 때문이다(둘 다 403 을
받고 알았다). 저장소 이름은 접미사 없는 쪽 그대로다.
브라우저 하네스
npm run build:browser # 하네스 방출
npm run check:browser # 하네스 타입만옆에 코어를 받아둘 필요가 없다. parity 와 train 이 쓰는 코어의 벤치 모듈이
borch-ts 0.2.2 부터 배포에 실린다(dist/test/bench·accuracy). 그전에는 옆에
받아둔 코어에서 가져올 수밖에 없었고, 그러면 코어가 두 벌이 되므로 하네스 전체를
그쪽으로 잡아야 했다 — 이제 벤치도 코어도 node_modules 에서 오고 한 벌이다.
하네스가 여전히 별도 tsconfig 인 것은 다른 이유다: @core/* 는 공개된 진입점이
아니다. 배포되는 src/ 는 그것을 몰라야 한다.
두 벌 대조
ResNet-18 정의는 코어의 벤치에도 있다. 합칠 수 없으므로(코어가 이 패키지를
의존하면 순환이다) 대신 대조한다 — 브라우저에서 둘을 만들어 stateDict 의
열쇠와 모양을 맞춰 본다.
npm run parity # 창 없이 돈다. 모양은 장치가 안 바꾼다값은 안 본다(각자 무작위 초기화라 같을 이유가 없다). 잡는 것은 필드 이름이
갈리는 것이다 — 코드로는 아무 문제 없고 타입 검사도 통과하는데, 이미 배포된
체크포인트만 조용히 안 실린다. 실제로 fc 를 head 로 바꿔 보면 컴파일러는
한마디도 안 하고 이 검사만 멈춘다.
브라우저를 띄우는 인자는 코어의 것을 그대로 들여온다(browser/launch.py).
여기서 잰 수는 코어의 벤치 수와 비교되기 위해 존재하고, 플래그가 다르면 그 비교가
거짓이 된다.
검사는 대조를 뺀 전부가 GPU 없이 돈다. 모델을 만드는 것은 WebGPU 어댑터가 필요하지만,
인자가 틀렸다는 것을 아는 데까지는 필요 없다 — 그래서 args.ts 가 registry.ts
에서 떨어져 있다.
관련
borch— 런타임 (코어)borch-hub-registry— 매니페스트와 검증 자료가 사는 곳
