느릿늘있

[WebGPU]에 대해서 Araboza...(4) 본문

개발공부

[WebGPU]에 대해서 Araboza...(4)

JHKim93 2023. 7. 30. 23:20
본 게시글은 W3C에 게시된 WebGPU Explainer 글을 학습을 위해 번역한 글입니다.

 

3.2 객체 유효성 및 파괴성
3.2.1 WebGPU의 Error Monad(단세포 생물)

  일명 전염성 내부 무효성(Contagious Internal Nullability), 투명한 promise 파이프라인

  WebGPU는 매우 chatty한( 수다스러운, 여기서는 요청이 많은) API입니다. 일부 프로그램은 복잡한 화면을 렌더링 하기 위해 프레임 당 수만 건의 호출을 요청합니다. 우리는 보안을 위해 해당 명령들의 유효성 검사를 해야했습니다. GPU 프로세스와 컨텐츠 프로세스에서 한 번에 두 번씩 검증하는 오버헤드를 피하기 위해, WebGPU는 Javascript 호출을 GPU 프로세스로 직접 전달하고 검증할 수 있도록 설계했습니다. 검증되는 내용과 어디서 어떻게 에러가 보고되는 지에 관한 세부 내용은 "에러 섹션"을 참조하십시오.

  동시에 단일 프레임에서 서로 의존하는 WebGPU 객체도 생성될 수 있습니다. 예를 들어, GPUCommandBuffer는 동일한 프레임에서 생성된 여러개의 임시 GPUBuffer들을 사용하는 명령어로 쓰일 수 있습니다. GPUCommandBuffer의 예시에서 WebGPU는 성능의 제약(※ 정확히 어떤 제약인지는 안나옴) 때문에 GPU 프로세스에 GPUBuffer를 생성하라는 메시지를 보낼 수 없고 또 Javascript 코드 실행 전에 그 프로세싱을 동기적으로 기다리라는 메시지도 보낼 수 없습니다.

  대신 WebGPU에서 GPUBuffer와 같은 모든 객체들은 content timeline에 즉시 생성되고 Javascript로 반환됩니다. 유효성 검사는 거의 모두 device timeline에서 비동기적으로 수행됩니다. 에러가 발생하지 않으면 JS가 마치 동기적으로 동작하는 것처럼 보일 것입니다. 하지만 에러가 발생하면 no-op이 됩니다. (※ no-op이 정확히 뭔지는 모르겠지만 안좋다는 뜻으로 넘어감.) 호출이 createBuffer와 같은 객체를 반환하면 그 객체는 GPU 프로세스 사이드에서 "invalid"로 처리됩니다.

  유효성 검사와 할당이 비동기적으로 동작하기 때문에, 오류 보고도 비동기적으로 발생합니다. 이는 디버깅을 어렵게 만듭니다... $3.3.1.1의 Debugging 내용을 참조하십시오.

  모든 WebGPU call은 해당 요청이 갖고 있는 모든 인수가 유효한 객체인지 유효성 검사를 수행합니다. 결과적으로 WebGPU call이 동일한 WebGPU 객체를 가져와서 새로 반환하면, 그 객체도 유효하지 않습니다. (따라서 "전염성(contagious)"이라는 단어를 사용합니다.)

// 동기적으로 동작하는 것처럼 보이는 사용 예시

// GPU버퍼 생성 -> device로 비동기 요청
const srcBuffer = device.createBuffer({
    size: 4,
    usage: GPUBufferUsage.COPY_SRC
});

// 목적지 버퍼 만들어서
const dstBuffer = ...;

// 인코더 만들어서
const encoder = device.createCommandEncoder();

// 인코더 활용해서 출발지 버퍼의 내용을 도착지 버퍼로 인코딩
encoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4);

// 인코딩 끝나면 device queue에 결과 제출
const commands = encoder.finish();
device.queue.submit([commands]);
// 출발지 버퍼의 사이즈가 너무 커서 에러가 발생하면?

// 에러 발생
const srcBuffer = device.createBuffer({
    size: BIG_NUMBER,
    usage: GPUBufferUsage.COPY_SRC
});

const dstBuffer = ...;

// 유효한 인코더 객체 생성(에러 없음)
const encoder = device.createCommandEncoder();

// 유효하지 않은 srcBuffer로 인해 유효한 인코더 객체에 에러 발생(전염)
encoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 4);

// 에러가 전염되어 finish()에서 유효하지 않은 요청으로 처리됨
const commands = encoder.finish();

// commands가 유효하지 않음으로 no-op!!이 되어버림!!
device.queue.submit([commands]);

3.2.1.1 Mental Models (※ 개념적 모델)

  이처럼 WebGPU의 코드 구현의 의미(semantics)를 해석하기 위한 한가지 관점은 모든 WebGPU 객체가 내부적으로 Promise 객체라는 것과 각 객체를 인수로 사용하기 전에 async/await로 처리된다고 보는 것입니다.(동기적으로 수행되는 것처럼 보임) 하지만 비동기 코드의 실행은 GPU 프로세스로 아웃소싱 됩니다. ( async/await는 비동기를 동기적으로 사용하기 위한 문법이다. 관련하여 제 블로그의 다른 글을 소개합니다...ㅎ_ㅎ) https://jhon-kim93.tistory.com/23

 

[SW_개념] 동기/비동기에 대한 주관적 개념 정리

한 줄 정리 싱크가 맞으면 동기(synchronous)고 싱크가 안맞으면 비동기(asynchronous)다. 동기/비동기 구분 동기/비동기 개념은 Javascript 개발자라면 무조건 들어봤을 개념이다. 많은 블로그에서 다루고

jhon-kim93.tistory.com

  이 의미를 해석함에 있어서 또 다른 방법(실제 구현과 더 가까운)은 각 Javascript GPU 객체가 boolean 타입의 isValid 값을 포함하는 GPU 프로세스의 gpu::internal C++/Rust 객체에 맵핑된다고 생각하는 것입니다. 그 다음 GPU 프로세스에서 각 명령(command)을 검증하면서 isValid 값이 모두 확인되고, 유효하지 않으면 새로운 invalid 객체가 반환됩니다. 콘텐츠 프로세스 측면에서 GPU 객체의 구현체는 그 객체가 유효한지 여부를 알지 못합니다. (※ 콘텐츠 프로세스 레벨의 JS WebGPU 객체가 좀 더 low한 레벨의 isValid 값을 갖고 있는 내장 객체로 맵핑되고 해당 객체가 유효성 검사를 수행한다. 성공 시 올바른 JS 객체를 리턴하여 정상 동작한다. 하지만 실패 시 JS 객체를 폐기하고 새로운 invalid 객체를 반환한다. 따라서 콘텐츠 프로세스 레벨에서 구현된 객체는 유효성에 대해 고민할 필요가 없다. 새로운 invalid 객체가 반환된 경우에  대해서 따로 처리해주면 된다.)