TECH 으로 돌아가기
TECH HACKER NEWS 오늘 6분 읽기 19 READS

CPU와 GPU가 다르게 세는 메모리, Facet으로 테스트에서 잡아내기

GPU 컴퓨트 셰이더를 다룰 때 호스트(CPU)와 셰이더가 설정값 같은 데이터를 공유하는 일은 흔하다. Rust에서는 zerocopy 애너테이션을 붙여 구조체에 as_bytes()를 호출하고, 그 바이트를 그대로 WebGPU 버퍼에 써 넣는 방식이 편리하게 쓰인다. 문제는 Rust와 WebGPU(WGSL)가 구조체 필드를 메모리에 배치하는 규칙이 미묘하게 다르다는 데 있다. 겉보기에는 멀쩡한 코드가 실제로는 두 쪽의 바이트 오프셋이 어긋나 엉뚱한 값을 읽는 상황이 벌어진다.

대표적인 사례가 3×3 행렬이다. WGSL의 mat3x3는 각 행마다 4바이트의 패딩이 붙어 한 행이 16바이트, 전체가 48바이트를 차지한다. 반면 Rust의 [[f32; 3]; 3]은 빈틈 없이 촘촘하게 채워져 36바이트에 그친다. 같은 '3×3 부동소수점 행렬'이라는 논리적 개념이 물리적으로는 12바이트나 차이 나는 것이다. 이런 종류의 어긋남은 컴파일러가 잡아주지 않기 때문에, 데이터를 통째로 복사하는 순간 조용히 깨진다.

재부팅으로 끝나는 실패

이 문제가 특히 고약해지는 지점은 글쓴이가 다루는 대상이 컴퓨트 셰이더 안에서 도는 바이트코드 VM이라는 데 있다. 레이아웃이 어긋나 잘못된 설정값이 셰이더로 들어가면, 실패 양상이 '틀린 결과를 낸다' 수준이 아니라 GPU에 무한히 도는 스레드가 남아 컴퓨터를 재부팅해야만 정리되는 상황으로 번진다. 한 번의 재부팅을 디버깅한 뒤, 그는 이 문제를 그때그때 손으로 잡는 대신 체계적으로 검증하기로 결정했다.

선택지는 이미 여럿 있었다. wgsl_to_wgpu, wgsl_bindgen, encase 같은 도구가 모두 이 영역과 관련이 있다. 다만 그는 외부 의존성과 빌드 스크립트를 더 늘리고 싶지 않아 직접 검증을 짜기로 했고, 방식으로는 단위 테스트를 골랐다. 이렇게 하면 일반적인 빌드에는 아무 부담을 주지 않는다는 장점이 있다. 물론 새 설정 객체를 추가하고 테스트를 깜빡하면 검증이 비는 실패 모드가 있지만, 그 정도 위험은 감수할 만하다고 봤다.

naga에서 Facet으로

첫 접근은 이미 셰이더 컴파일용으로 의존성 트리에 들어 있는 naga를 활용하는 것이었다. naga로 WGSL 구조체의 레이아웃을 뽑아낸 뒤, Rust 쪽 Config 구조체의 각 멤버와 크기·오프셋을 하나씩 대조한다. 이 방식은 동작하긴 했지만, 필드마다 대조 코드를 손으로 반복해서 써야 한다는 번거로움이 남았다.

그래서 등장하는 것이 Facet이다. Facet은 Rust에 런타임 리플렉션을 제공하는 라이브러리로, 구조체에 #[derive(facet::Facet)]를 붙이면 런타임에 들여다볼 수 있는 SHAPE 연관 타입이 생긴다. 이를 이용하면 T: facet::Facet으로 매개변수화된 하나의 제네릭 검사 함수를 만들 수 있다. 함수는 셰이더를 파싱해 이름으로 설정 구조체를 찾고, 전체 크기와 각 필드의 크기·오프셋을 Facet의 인트로스펙션 데이터와 맞대어 검사한다. 나아가 Rust 필드가 WGSL에 다 있는지뿐 아니라, 모든 WGSL 멤버가 Rust 구조체에도 존재하는지 양방향으로 확인한다.

여기서 신경 쓴 지점이 런타임 크기 배열이다. 글쓴이는 설정 객체의 마지막 멤버로 크기가 실행 시점에 정해지는 배열을 자주 쓰는데, 이는 C의 유연 배열 멤버(flexible array member)나 Rust의 동적 크기 타입에 해당한다. Rust 쪽 구조체를 정의할 때는 이 마지막 멤버를 생략하므로, naga가 런타임 크기 배열을 보고하면 전체 크기를 비교하는 대신 그 배열의 오프셋이 Rust 구조체 크기와 일치하는지를 확인하도록 처리했다.

제네릭 검사 함수를 짜는 데는 다소 손이 갔지만, 한번 완성하고 나니 모든 설정 객체에 손쉽게 적용할 수 있었다. Facet은 단위 테스트에서만 조건부로 derive하도록 해 실제 배포 빌드에는 영향을 주지 않는다. 실제로 이 테스트들은 기존 설정 객체에서 새로운 문제를 찾아내지는 못했는데, 원래 모든 게 잘 돌아가고 있었으니 놀라운 일은 아니다. 진짜 가치는 앞으로에 있다. 새 컴퓨트 셰이더를 추가할 때 데이터 로딩의 정확성을 훨씬 큰 확신을 갖고 다룰 수 있게 된 것이다.

실무적으로 이 사례가 시사하는 바는 분명하다. CPU와 GPU 사이의 데이터 교환은 타입 시스템이 보장해 주지 않는 회색 지대이고, 정렬·패딩 규칙 차이는 언제든 조용한 버그로 이어진다. 그 검증을 빌드 파이프라인에 무겁게 붙이는 대신 리플렉션과 단위 테스트로 얇게 얹는 접근은, 외부 도구 도입이 부담스러운 프로젝트에 참고할 만한 균형점이다. 다만 테스트 기반 검증은 '작성한 만큼만' 보장한다는 한계가 있어, 새 객체를 추가할 때 테스트를 함께 넣는 규율이 전제되어야 한다는 점은 잊지 말아야 한다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://www.mattkeeter.com/blog/2026-08-23-wgpu-facet/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...