- vừa được xem lúc

GPU programming với Golang

0 0 26

Người đăng: Huy Tran

Theo The Full Snack

GPU programming với Golang

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à $mumax^{3}$ 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++:

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ình luận

Bài viết tương tự

- vừa được xem lúc

Thuật toán quay lui (Backtracking)

Quay lui là một kĩ thuật thiết kế giải thuật dựa trên đệ quy. Ý tưởng của quay lui là tìm lời giải từng bước, mỗi bước chọn một trong số các lựa chọn khả dĩ và đệ quy.

0 0 47

- vừa được xem lúc

Các thuật toán cơ bản trong AI - Phân biệt Best First Search và Uniform Cost Search (UCS)

Nếu bạn từng đọc các thuật toán trong AI (Artificial Intelligence - Trí tuệ nhân tạo), rất có thể bạn từng nghe qua về các thuật toán tìm kiếm cơ bản: UCS (thuộc chiến lược tìm kiếm mù) và Best First Search (thuộc chiến lược tìm kiếm kinh nghiệm). Khác nhau rõ từ khâu phân loại rồi, thế nhưng hai th

0 0 164

- vừa được xem lúc

Sử dụng vector trong lập trình C++ - giải bài toán lập trình muôn thủa

Chào buổi tối mọi người, hôm nay lang thang trên mạng bắt gặp bài toán quen thuộc một thời của quãng đường sinh viên IT. Đấy chính là câu số 1 trong đề thi dưới đây:.

0 0 48

- vừa được xem lúc

MÔ PHỎNG THUẬT TOÁN VƯƠNG HẠO TRONG PROLOG

. 1. Các luật suy diễn trong thuật toán Vương Hạo. Luật 1: Chuyển vế các giả thuyết và kết luận ở dạng phủ định. Ví dụ: p v q, !(r ^ s), !q, p v r -> s, !p <=> p v q, p v r, p -> s, r ^ s, q.

0 0 83

- vừa được xem lúc

A* Search Algorithm

What is A* Search Algorithm. How it works. . Explanation.

0 0 55

- vừa được xem lúc

Python: Jump Search

Search là một từ khóa khá là quen thuộc đối với chúng ta. Hiểu theo đúng nghĩa đen của nó chính là "Tìm kiếm".

0 0 44