GPU programming với Golang

Công Nghệ
GPU programming với Golang
Bài viết được sự cho phép của tác giả Huy Trần Ở bài trước mình có giới thiệu về kĩ thuật lập trình GPU với OpenCL bằng C/C++ . Hôm nay mình sẽ giới thiệu tiếp kĩ thuật này trên Go. Sử dụng C/C++ trong Go Một đặc điểm của...

Bài viết được sự cho phép của tác giả Huy Trần

Ở bài trước mình có giới thiệu về kĩ thuật lập trình GPU với OpenCL bằng C/C++. Hôm nay mình sẽ giới thiệu tiếp kĩ thuật này trên Go.

Sử dụng C/C++ trong Go

Một đặc điểm của Golang là chúng ta có thể thoải mái import các thư viện C/C++ và biên dịch bằng sự hỗ trợ của Cgo, các bác có thể xem lại bài Một số kinh nghiệm làm việc với Cgo để biết thêm chi tiết.

Về cách sử dụng thì chúng ta chỉ đơn giản là viết đoạn code C/C++ trong phần comment đầu file và biên dịch:

package main
 
/*
#include <stdio.h>
 
void sayHello() {
  printf("YOLO!")
}
*/
import "C"
 
func main() {
  C.sayHello()
}

Cho nên chúng ta hoàn toàn có thể include thư viện OpenCL từ bên phía C/C++ vào Go để chạy bằng Cgo. Tất nhiên phải chỉ định framework cần dùng ở đầu chương trình luôn, ví dụ:

/*
#cgo CFLAGS: -Wall
#cgo LDFLAGS: -framework opencl
#include <OpenCL/opencl.h>
*/

Hoặc với CUDA như sau:

//#include <cuda.h>
//#cgo LDFLAGS: -lcuda

Và viết code cho Host Program như bình thường.

Kernel Program

Bạn có thể lập trình cho Host Program bằng Golang, tuy nhiên, đối với Kernel Program thì nó vẫn là một file .cl và nó vẫn phải dùng cú pháp của C, hoàn toàn không có sự thay đổi nào ở đây cả.

Sử dụng Go Wrappers

Nếu cảm thấy việc khai báo cú pháp theo Cgo là hơi phức tạp và gây rối cho chương trình thì bạn có thể sử dụng các wrapper có sẵn mà cộng đồng Golang đã xây dựng, bản chất các wrapper này vẫn là sử dụng Cgo không có gì khác, chúng chỉ cung cấp cho bạn cú pháp dễ nhìn hơn và dễ build hơn mà thôi.

CUDA Wrapper cho Go

Bài viết này không có ý định tìm hiểu sâu hơn về CUDA cho nên mình sẽ dẫn link để các bạn tham khảo.

Tác giả Arne Vansteenkiste có viết một thư viện tên là mumax3 hỗ trợ lập trình GPU bằng CUDA, các bạn không nhất thiết phải sử dụng MuMax tuy nhiên có thể sử dụng CUDA wrapper mà MuMax cung cấp.

package main
 
import "github.com/mumax/3/cuda"
 
func main(){
  N := 3
  a := cuda.NewSlice(N)
  b := cuda.NewSlice(N)
  c := cuda.NewSlice(N)
  defer a.Free()
  defer b.Free()
  defer c.Free()
 
  a.CopyHtoD([]float32{0, -1, -2})
  b.CopyHtoD([]float32{0, 1, 4})
 
  cfg := Make1DConfig(N)
  add_kernel(a.Ptr(), b.Ptr(), c.Ptr(), cfg)
 
  fmt.Println("result:", a.HostCopy())
}

Ngoài ra các bạn có thể tham khảo thêm slide Scientific GPU computing with Go để biết thêm chi tiết về framework này.

OpenCL

Với OpenCL thì chúng ta cũng có kha khá là nhiều các wrapper, tuy nhiên ở đây mình chọn sử dụng go-gl/cl vì dự án này được phát triển khá là nghiêm túc và rất active, tài liệu + community đầy đủ.

Cách sử dụng thì rất đơn giản, đầu tiên bạn chỉ cần cài đặt gói go-gl/cl mới nhất, hoặc là bản stable nhất (là bản v1.2):

go get github.com/go-gl/cl/v1.2/cl

Hoặc có thể download và compile lại gói này với lệnh sau để đạt performance tốt hơn:

go install -gcflags="-l -l -l -l" github.com/go-gl/cl/v1.2/cl

Lệnh trên sẽ inline hầu hết mọi function trong package go-gl/cl và vì việc gọi function thì rất tốn kém (xem ở đây), nên inline sẽ giúp giảm thiểu chi phí cho vụ này, tuy nhiên nhược điểm của cách này là dung lượng binary khi build sẽ lớn hơn nhiều.

Cách dùng thì chỉ cần import thư viện trên vào code, và gọi hàm thông qua đối tượng cl:

import "github.com/go-gl/cl/v1.2/cl"
...
cl.CreateKernel(program, cl.Str("hello"), errptr)

Các bạn có thể xem qua đoạn chương trình mẫu tính bình phương một số để hiểu thêm về cách dùng, cũng tương tự như với C/C++:

Xem thêm tuyển dụng Golang hấp dẫn trên Station D

Mình lượt bỏ các phần linh tinh và ghi lại các phần chính cần lưu ý trong chương trình:

Import thư viện go-gl/cl:

package main
 
import "github.com/go-gl/cl/v1.2/cl"

Khai báo và biên dịch Kernel:

var KernelSource = `
__kernel void square(
   __global float* input,
   __global float* output,
   const unsigned int count)
{
   int i = get_global_id(0);
   if(i < count)
	   output[i] = input[i] * input[i];
}` + "x00"
 
...
 
 
// Đọc và biên dịch kernel program
srcptr := cl.Str(KernelSource)
program := cl.CreateProgramWithSource(context, 1, &srcptr, nil, errptr)
defer cl.ReleaseProgram(program)
 
err = cl.BuildProgram(program, 1, &device, nil, nil, nil)

Lấy thông tin Compute Device:

err := cl.GetDeviceIDs(nil, cl.DEVICE_TYPE_GPU, 1, &device, nil)

Khởi tạo Context:

context := cl.CreateContext(nil, 1, &device, nil, nil, errptr)
defer cl.ReleaseContext(context)

Khởi tạo Command Queue:

cq := cl.CreateCommandQueue(context, device, 0, errptr)
defer cl.ReleaseCommandQueue(cq)

Khởi tạo Kernel Object:

kernel := cl.CreateKernel(program, cl.Str("square"+"x00"), errptr)
defer cl.ReleaseKernel(kernel)

Khởi tạo Memory Object:

input := cl.CreateBuffer(context, cl.MEM_READ_ONLY, 4*DataSize, nil, errptr)
defer cl.ReleaseMemObject(input)
 
output := cl.CreateBuffer(context, cl.MEM_WRITE_ONLY, 4*DataSize, nil, errptr)
defer cl.ReleaseMemObject(output)

Ghi dữ liệu vào Memory Object trong Device:

err = cl.EnqueueWriteBuffer(cq, input, cl.TRUE, 0, 4*DataSize, unsafe.Pointer(&data[0]), 0, nil, nil)
err = cl.SetKernelArg(kernel, 0, 8, unsafe.Pointer(&input))
err = cl.SetKernelArg(kernel, 1, 8, unsafe.Pointer(&output))
err = cl.SetKernelArg(kernel, 2, 4, unsafe.Pointer(&count))

Khởi tạo Work Group để bắt đầu chạy Kernel:

err = cl.GetKernelWorkGroupInfo(kernel, device, cl.KERNEL_WORK_GROUP_SIZE, 8, unsafe.Pointer(&local), nil)
err = cl.EnqueueNDRangeKernel(cq, kernel, 1, nil, &global, &local, 0, nil, nil)
cl.Finish(cq)

Đọc dữ liệu ra bằng Channel:

results := make([]float32, DataSize)
err = cl.EnqueueReadBuffer(cq, output, cl.TRUE, 0, 4*1024, unsafe.Pointer(&results[0]), 0, nil, nil)

Ở chương trình trên thì có sự xuất hiện của kí tự x00 ở phần kernel source, đây là kí tự NULL dùng để báo hiệu kết thúc string.

Và chúng ta cũng thấy, ở đây ta có thể tận dụng luôn các chức năng mà Go cung cấp một cách rất hiệu quả như là defer hoặc channel,…

Bài viết gốc được đăng tải tại snacky.blog

Có thể bạn quan tâm:

Xem thêm các việc làm công nghệ hấp dẫn trên Station D

Bài viết liên quan

Ngành IT: Làm việc “trên mây” kiếm nhiều tiền nhất hiện nay

Ngành IT: Làm việc “trên mây” kiếm nhiều tiền nhất hiện nay

Kết quả từ cuộc khảo sát đầu năm của Station D về lương bổng của lập trình viên cho thấy nhiều thay đổi đã và đang diễn ra trong ngành IT – cuộc khảo sát tập trung vào các câu hỏi về khối lượng công việc, triển vọng cũng như...

By stationd
Đâu chỉ mỗi Bitcoin, công nghệ Blockchain còn nhiều ứng dụng hơn thế!

Đâu chỉ mỗi Bitcoin, công nghệ Blockchain còn nhiều ứng dụng hơn thế!

Khi nhắc đến blockchain , lập tức mọi người thường nghĩ ngay đến các loại tiền mã hóa, chẳng hạn như bitcoin. Tuy nhiên, blockchain lại là công nghệ tạo ra tiền mã hóa nhưng bản thân công nghệ này không phải là tiền mã hóa như cách mà chúng...

By stationd
Mock phương thức static trong Unit Test sử dụng PowerMock

Mock phương thức static trong Unit Test sử dụng PowerMock

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh Trong bài viết này, mình sẽ hướng dẫn các bạn Mock các phương thức static trong Unit Test các bạn nhé! Nếu bạn nào chưa biết về Mock trong Unit Test thì mình có thể nói sơ qua...

By stationd