WebGPU를 이용한 계산(Computing)

3개의 실수값을 전달하고 이 값에 2를 곱한 결과를 GPU를 통해 병렬로 처리 실행해 그 결과를 얻는 코드입니다. 먼저 GPU에 대한 객체를 얻는 것은 비동기로 처리해야 하므로 다음과 같은 코드가 필요합니다.

async function main() {
  // 여기에 이후 모든 코드가 작성됩니다.
}

await main();

자, 이제 GPU에 대한 객체를 얻습니다.

  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();

실행할 쉐이더 코드를 작성합니다. 쉐이더 코드는 WGSL로 작성됩니다.

  const module = device.createShaderModule({
    label: 'doubling compute module',
    code: /* wgsl */ `
      @group(0) @binding(0) var<storage, read_write> data: array<f32>;
 
      @compute @workgroup_size(1) fn computeSomething(
        @builtin(global_invocation_id) id: vec3u
      ) {
        let i = id.x;
        data[i] = data[i] * 2.0;
      }
    `,
  });

위의 쉐이더 코드를 실행할 파이프라인 객체를 생성합니다.

  const pipeline = device.createComputePipeline({
    label: 'x2 compute pipeline',
    layout: 'auto',
    compute: {
      module,
      entryPoint: 'computeSomething', // 실행할 쉐이더 함수
    },
  });

실행할 쉐이더 함수를 보면 1개의 인자를 받는데, 그 인자를 정의합니다.

  const input = new Float32Array([1, 3, 5]);

위의 인자는 CPU 메모리에 있으므로 이를 GPU 메모리에 만들어서 복사해 줘야 합니다.

  const workBuffer = device.createBuffer({
    label: 'work buffer',
    size: input.byteLength,
    usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
  });

  device.queue.writeBuffer(workBuffer, 0, input);

GPU를 통해 계산된 최종 결과를 읽기 위한 사본 버퍼를 생성합니다.

  const resultBuffer = device.createBuffer({
    label: 'result buffer',
    size: input.byteLength,
    usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
  });

계산을 위해 사용될 인자에 대한 버퍼가 무엇인지를 명확히 지정합니다.

  const bindGroup = device.createBindGroup({
    label: 'bindGroup for work buffer',
    layout: pipeline.getBindGroupLayout(0),
    entries: [
      { binding: 0, resource: { buffer: workBuffer } },
    ],
  });

GPU에서 실행할 명령을 인코딩합니다.

  const encoder = device.createCommandEncoder({ label: 'x2 encoder' });
  const pass = encoder.beginComputePass({ label: 'x2 compute pass' });

  pass.setPipeline(pipeline);
  pass.setBindGroup(0, bindGroup);
  pass.dispatchWorkgroups(input.length);
  pass.end();

계산 결과를 읽어 내기 위해 맵핑용 버퍼에 복사하는 명령을 인코딩합니다.

  encoder.copyBufferToBuffer(workBuffer, 0, resultBuffer, 0, resultBuffer.size);

실해 명령의 실행은 다음 코드를 통해 이루어집니다.

   const commandBuffer = encoder.finish();
   device.queue.submit([commandBuffer]);

실행 결과를 콘솔에 출력하기 위한 코드입니다.

  await resultBuffer.mapAsync(GPUMapMode.READ);
  const result = new Float32Array(resultBuffer.getMappedRange());
 
  console.log('input', input);
  console.log('result', result);
 
  resultBuffer.unmap();

실행 결과를 보면 다음과 같습니다.

실행 결과를 보면 입력값에 2배만큼 곱해졌는데, 이는 GPU를 통해 병렬로 계산된 것입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다