cv::gemm()のOpenCL実装が小さな行列に対応してなかった件

OpenCV 3.4.0での話。OpenCL有効化済みのOpenCVAMD GPU環境で実行。OSはWin10。

以下のソースコードで行列積を計算しようとしたら結果がぐちゃぐちゃになっていた。

ソースコード

#include <opencv2/core.hpp>
#include <iostream>
int main(void)
{

	constexpr int input_size = 4;  // 行列の行数、列数。これを16にすると期待する計算結果になる
	cv::Size size(input_size, input_size);

	// 入力
	cv::Mat cpu_a(size, CV_32F);
	cv::Mat cpu_b(size, CV_32F);

	// all 0
	cv::Mat  cpu_zero = cv::Mat::zeros(size, CV_32F);
	cv::UMat gpu_zero = cpu_zero.getUMat(cv::ACCESS_READ);

	// 出力
	cv::Mat  cpu_dst(size, CV_32F);
	cv::UMat gpu_dst(size, CV_32F);

	// 入力の行列を一様分布の乱数0.0〜1.0で埋める
	cv::randu(cpu_a, cv::Scalar(0.0f), cv::Scalar(1.0f));
	cv::randu(cpu_b, cv::Scalar(0.0f), cv::Scalar(1.0f));
	cv::UMat gpu_a = cpu_a.getUMat(cv::ACCESS_READ);
	cv::UMat gpu_b = cpu_b.getUMat(cv::ACCESS_READ);

	// 行列積を計算する
	cv::gemm(cpu_a, cpu_b, 1.0, cpu_zero, 1.0, cpu_dst);  // CPU版が呼ばれる
	cv::gemm(gpu_a, gpu_b, 1.0, gpu_zero, 1.0, gpu_dst);  // OpenCL版が呼ばれる

	// CPU版とGPU版の誤差(最大、最小)を表示する
	double min_e, max_e;
	cv::minMaxLoc(cpu_dst - gpu_dst.getMat(cv::ACCESS_READ), &min_e, &max_e);
	std::cout << min_e << ", " << max_e << std::endl;

	return 0;
}

現象

CodeXLで確認するとGlobalWorkSizeが(1, 16, 1)、WorkGroupSizeが(16, 16, 1)となっていた。Globalって確かGroupの倍数じゃないとエラーになったような気がしたけど、実行してもエラーにはならなかった。
代わりに計算結果が合わない。行列Bの2行目以降をall 0にすると計算結果が期待する値になったので、アクセス先のアドレスがおかしくなってるのかも。

OpenCVの実装

OpenCVのソースを確認したところ、

int block_size = (max_wg_size / (32*cn) < 32) ? (max_wg_size / (16*cn) < 16) ? (max_wg_size / (8*cn) < 8) ? 1 : 8 : 16 : 32;

という感じでOpenCLのMaxWorkGroupSizeが

  • 1024以上なら32
  • 256以上なら16
  • 64以上なら8

の固定値がWorkGroupSizeとしてわたされていた。(float 1channelの場合)
dst行列のサイズはガン無視かーい!あ、GlobalWorkSizeはdst行列のサイズをベースにしてますねぇ。

使っているGPUのMaxWorkGroupSizeは256なので行数、列数を最低16にしないとおかしくなりそうな感じだった。

以上

ということで、16に変更したらあっさり動きましたとさ。というかこれ、自分の環境だと16の倍数以外で行列渡したらどうなるかわからないなぁ・・・
苦労して解析した割には、しょうもない理由だった。ま、いいや


ちょっと動かして使い方を確認したかっただけだったのにごっそり時間を持っていかれた・・・orz