Coelacanth's Dream

次世代 GPU IPブロックのサポートが進む AMDGPUドライバー ―― GFX11, GC 11.0, MES 11.0, IMU

先日の SoC21 に続き、次世代 AMD GPU に採用されるであろう IPブロックのサポートに向けたパッチが、AMD の Alex Deucher 氏より amd-gfx メーリングリストに投稿されている。
詳細は後述するが、AMDGPUドライバーでは IPブロックごとのサポートに移行したのに加え、GPU 情報がよりハードウェア側のバイナリ部分から読み取れるようになったことで、ソースコードから得られる情報は少なくなりつつある。

Index

GC 11.0

GC 11.0/GFX11 の特徴として、GFX, SDMA, Compute の HWスケジューリングエンジンとして MES 11.0 (Micro Engine Scheduler) を採用すること、
新たに電力管理ユニットとして、IMU を採用することが挙げられる。

MES

MES は従来の HWS (HardWare Scheduler) に近い機能を持つ。
用語の解説を挟むと、まず AMD GPU にはハードウェアブロックとして CP (Command Processor) があり、CP は多数のマイクロコントローラーで構成されている。その一部が MEC (MicroEngine Compute) であり、HWS は MEC内に位置する。
MES は MEC を置き換えるものと思われるが、今の所パッチ内などで明言はされていない。

MES は RDNA 1/GFX10 世代から搭載されていたが、デフォルトでは無効化されていた。AMDGPUドライバー側のサポートが進んでいなかったことが理由の一つにあると思われる。
今回のパッチでサポートが進んだものの、デフォルトでは無効化されている点は変更されていない。

 /**
  * DOC: mes (int)
  * Enable Micro Engine Scheduler. This is a new hw scheduling engine for gfx, sdma, and compute.
  * (0 = disabled (default), 1 = enabled)
  */
 MODULE_PARM_DESC(mes,
 	"Enable Micro Engine Scheduler (0 = disabled (default), 1 = enabled)");
 module_param_named(mes, amdgpu_mes, int, 0444);

IMU

IMU が何の略語なのかは触れられていないが、パッチのコメントから GFXコア部のみを対象に絞った電力管理ブロック/ユニットであることが考えられる。
RDNA 2/GFX10.3 世代では、CU数が非対称な ShaderArray における電力効率を向上させる機能が追加されるなど、以前から GFXコア部を対象にした電力管理機能が強化されている節はあった。
AMD RDNA 2 ノート ―― 非対称な ShaderArray、gpu_info、XSS 【2020/09/23】 | Coelacanth’s Dream

 Add support to initialize imu for gfx v11.
 IMU is a new power management block for
 gfx which manages gfx power.

Other

GC 11.0/GFX11 世代では最大 ShaderEngine 8基 (ShaderArray 16基) の構成に対応し、一部無効化された CU を示すマスクも 16基分が想定されている。
RDNA 1/RDNA 2 世代における同様の処理は gfx_v10_0.c に記述されているが、それらの世代では最大 ShaderEngine 4基 (ShaderArray 8基) までの対応となっている。1

 +static int gfx_v11_0_get_cu_info(struct amdgpu_device *adev,
 +				 struct amdgpu_cu_info *cu_info)
 +{
 +	int i, j, k, counter, active_cu_number = 0;
 +	u32 mask, bitmap;
 +	unsigned disable_masks[8 * 2];
 +
 +	if (!adev || !cu_info)
 +		return -EINVAL;
 +
 +	amdgpu_gfx_parse_disable_cu(disable_masks, 8, 2);
 +
 +	mutex_lock(&adev->grbm_idx_mutex);
 +	for (i = 0; i < adev->gfx.config.max_shader_engines; i++) {
 +		for (j = 0; j < adev->gfx.config.max_sh_per_se; j++) {
 +			mask = 1;
 +			counter = 0;
 +			gfx_v11_0_select_se_sh(adev, i, j, 0xffffffff);
 +			if (i < 8 && j < 2)
 +				gfx_v11_0_set_user_wgp_inactive_bitmap_per_sh(
 +					adev, disable_masks[i * 2 + j]);
 +			bitmap = gfx_v11_0_get_cu_active_bitmap_per_sh(adev);
 +
 +			/**
 +			 * GFX11 could support more than 4 SEs, while the bitmap
 +			 * in cu_info struct is 4x4 and ioctl interface struct
 +			 * drm_amdgpu_info_device should keep stable.
 +			 * So we use last two columns of bitmap to store cu mask for
 +			 * SEs 4 to 7, the layout of the bitmap is as below:
 +			 *    SE0: {SH0,SH1} --> {bitmap[0][0], bitmap[0][1]}
 +			 *    SE1: {SH0,SH1} --> {bitmap[1][0], bitmap[1][1]}
 +			 *    SE2: {SH0,SH1} --> {bitmap[2][0], bitmap[2][1]}
 +			 *    SE3: {SH0,SH1} --> {bitmap[3][0], bitmap[3][1]}
 +			 *    SE4: {SH0,SH1} --> {bitmap[0][2], bitmap[0][3]}
 +			 *    SE5: {SH0,SH1} --> {bitmap[1][2], bitmap[1][3]}
 +			 *    SE6: {SH0,SH1} --> {bitmap[2][2], bitmap[2][3]}
 +			 *    SE7: {SH0,SH1} --> {bitmap[3][2], bitmap[3][3]}
 +			 */
 +			cu_info->bitmap[i % 4][j + (i / 4) * 2] = bitmap;
 +
 +			for (k = 0; k < adev->gfx.config.max_cu_per_sh; k++) {
 +				if (bitmap & mask)
 +					counter++;
 +
 +				mask <<= 1;
 +			}
 +			active_cu_number += counter;
 +		}
 +	}
 +	gfx_v11_0_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff);
 +	mutex_unlock(&adev->grbm_idx_mutex);
 +
 +	cu_info->number = active_cu_number;
 +	cu_info->simd_per_cu = NUM_SIMD_PER_CU;
 +
 +	return 0;
 +}

以下の引用部から、WGP 1基あたりの CU数は 2基という構成は GC 11.0/GFX11 でも変わらないと考えられる。

 +static u32 gfx_v11_0_get_cu_active_bitmap_per_sh(struct amdgpu_device *adev)
 +{
 +	u32 wgp_idx, wgp_active_bitmap;
 +	u32 cu_bitmap_per_wgp, cu_active_bitmap;
 +
 +	wgp_active_bitmap = gfx_v11_0_get_wgp_active_bitmap_per_sh(adev);
 +	cu_active_bitmap = 0;
 +
 +	for (wgp_idx = 0; wgp_idx < 16; wgp_idx++) {
 +		/* if there is one WGP enabled, it means 2 CUs will be enabled */
 +		cu_bitmap_per_wgp = 3 << (2 * wgp_idx);
 +		if (wgp_active_bitmap & (1 << wgp_idx))
 +			cu_active_bitmap |= cu_bitmap_per_wgp;
 +	}
 +
 +	return cu_active_bitmap;
 +}
 +

CU内の SIMDユニットに関する記述もある。ここでは navi10_enum.h ファイルを include しているため、NUM_SIMD_PER_CU には RDNA 1/RDNA 2 世代と同じ 0x2 が入る。2

 +	cu_info->number = active_cu_number;
 +	cu_info->simd_per_cu = NUM_SIMD_PER_CU;
 +
 +	return 0;
 +}

IP discovery

AMD GPU では Vega/GFX9 世代から、電源投入時に PSP (Platform Security Processor) が内部的に GPU 情報を出力する IP discovery table に対応している。
IP discovery table は世代ごとに拡張されており、AMDGPUドライバーでは最近になって、GPU あたりの GL2キャッシュ、ShaderArray あたりの GL1キャッシュ の数とサイズ等の情報が追加された gc_info_v1_2 に対応するパッチが投稿されている。

 +struct gc_info_v1_2 {
 +	struct gpu_info_header header;
 +	uint32_t gc_num_se;
 +	uint32_t gc_num_wgp0_per_sa;
 +	uint32_t gc_num_wgp1_per_sa;
 +	uint32_t gc_num_rb_per_se;
 +	uint32_t gc_num_gl2c;
 +	uint32_t gc_num_gprs;
 +	uint32_t gc_num_max_gs_thds;
 +	uint32_t gc_gs_table_depth;
 +	uint32_t gc_gsprim_buff_depth;
 +	uint32_t gc_parameter_cache_depth;
 +	uint32_t gc_double_offchip_lds_buffer;
 +	uint32_t gc_wave_size;
 +	uint32_t gc_max_waves_per_simd;
 +	uint32_t gc_max_scratch_slots_per_cu;
 +	uint32_t gc_lds_size;
 +	uint32_t gc_num_sc_per_se;
 +	uint32_t gc_num_sa_per_se;
 +	uint32_t gc_num_packer_per_sc;
 +	uint32_t gc_num_gl2a;
 +	uint32_t gc_num_tcp_per_sa;
 +	uint32_t gc_num_sdp_interface;
 +	uint32_t gc_num_tcps;
 +	uint32_t gc_num_tcp_per_wpg;
 +	uint32_t gc_tcp_l1_size;
 +	uint32_t gc_num_sqc_per_wgp;
 +	uint32_t gc_l1_instruction_cache_size_per_sqc;
 +	uint32_t gc_l1_data_cache_size_per_sqc;
 +	uint32_t gc_gl1c_per_sa;
 +	uint32_t gc_gl1c_size_per_instance;
 +	uint32_t gc_gl2c_per_gpu;
 +};

RDNA 2/GFX10.3 世代ではキャッシュ情報がソースコード内にハードコードされていたが、gc_info_v1_2 への対応によりその必要がなくなる。ソースコードから得られる AMD GPU の情報は少なくなるが、その方がドライバーとしては効率的である。

その他にも、MALL/Infinity Cache と VCN の構成情報を取得することが可能になった。
MALL/Infinity Cache では、メモリチャネルあたりのキャッシュサイズ (mall_size_per_m)、その倍のサイズ、もしくは半分のみを使用可能とするかのフラグ (m_s_present, m_half_use) といった情報が出力される。
今後はメモリチャネル数は変えずに MALL/Infinity Cache のサイズを変更した SKU も可能になると思われるが、実際に計画されているかは当然不明。

 +struct mall_info_v1_0 {
 +	struct mall_info_header header;
 +	uint32_t mall_size_per_m;
 +	uint32_t m_s_present;
 +	uint32_t m_half_use;
 +	uint32_t m_mall_config;
 +	uint32_t reserved[5];
 +};
 +
 +	case 1:
 +		mall_size = 0;
 +		mall_size_per_umc = le32_to_cpu(mall_info->v1.mall_size_per_m);
 +		m_s_present = le32_to_cpu(mall_info->v1.m_s_present);
 +		half_use = le32_to_cpu(mall_info->v1.m_half_use);
 +		for (u = 0; u < adev->gmc.num_umc; u++) {
 +			if (m_s_present & (1 << u))
 +				mall_size += mall_size_per_umc * 2;
 +			else if (half_use & (1 << u))
 +				mall_size += mall_size_per_umc / 2;
 +			else
 +				mall_size += mall_size_per_umc;
 +		}
 +		adev->gmc.mall_size = mall_size;
 +		break;

VCN では、インスタンス数と、恐らくはどの対応コーデックが無効化されているか、といった情報が出力される。エンコーダーの有無等の情報は現時点では見当たらない。

 +#define VCN_INFO_TABLE_MAX_NUM_INSTANCES 4
 +
 +struct vcn_info_header {
 +    uint32_t table_id; /* table ID */
 +    uint16_t version_major; /* table version */
 +    uint16_t version_minor; /* table version */
 +    uint32_t size_bytes; /* size of the entire header+data in bytes */
 +};
 +
 +struct vcn_instance_info_v1_0
 +{
 +	uint32_t instance_num; /* VCN IP instance number. 0 - VCN0; 1 - VCN1 etc*/
 +	union _fuse_data {
 +		struct {
 +			uint32_t av1_disabled : 1;
 +			uint32_t vp9_disabled : 1;
 +			uint32_t hevc_disabled : 1;
 +			uint32_t h264_disabled : 1;
 +			uint32_t reserved : 28;
 +		} bits;
 +		uint32_t all_bits;
 +	} fuse_data;
 +	uint32_t reserved[2];
 +};
 +
 +struct vcn_info_v1_0 {
 +	struct vcn_info_header header;
 +	uint32_t num_of_instances; /* number of entries used in instance_info below*/
 +	struct vcn_instance_info_v1_0 instance_info[VCN_INFO_TABLE_MAX_NUM_INSTANCES];
 +	uint32_t reserved[4];
 +};
 +

Cache info

以下引用部は、IP discovery table からキャッシュ情報を読み取った場合の処理となる。
まず、CU ごとに持つプライベートキャッシュ (TCP, Texture Cache per Pipe) だが、共有する CU数を示す num_cu_shared には WGP あたりの数を 2 で割っている。
このことから WGP あたりの CU数は 2 で変わらないことが考えられる。
ソースコード中では WGP ではなく、“WPG” になっているが、ここでは一旦誤字とする。

 +static int kfd_fill_gpu_cache_info_from_gfx_config(struct kfd_dev *kdev,
 +						   struct kfd_gpu_cache_info *pcache_info)
 +{
 +	struct amdgpu_device *adev = kdev->adev;
 +	int i = 0;
 +
 +	/* TCP L1 Cache per CU */
 +	if (adev->gfx.config.gc_tcp_l1_size) {
 +		pcache_info[i].cache_size = adev->gfx.config.gc_tcp_l1_size;
 +		pcache_info[i].cache_level = 1;
 +		pcache_info[i].flags = (CRAT_CACHE_FLAGS_ENABLED |
 +					CRAT_CACHE_FLAGS_DATA_CACHE |
 +					CRAT_CACHE_FLAGS_SIMD_CACHE);
 +		pcache_info[0].num_cu_shared = adev->gfx.config.gc_num_tcp_per_wpg / 2;
 +		i++;
 +	}
 +	/* Scalar L1 Instruction Cache per SQC */
 +	if (adev->gfx.config.gc_l1_instruction_cache_size_per_sqc) {
 +		pcache_info[i].cache_size =
 +			adev->gfx.config.gc_l1_instruction_cache_size_per_sqc;
 +		pcache_info[i].cache_level = 1;
 +		pcache_info[i].flags = (CRAT_CACHE_FLAGS_ENABLED |
 +					CRAT_CACHE_FLAGS_INST_CACHE |
 +					CRAT_CACHE_FLAGS_SIMD_CACHE);
 +		pcache_info[i].num_cu_shared = adev->gfx.config.gc_num_sqc_per_wgp * 2;
 +		i++;
 +	}
 +	/* Scalar L1 Data Cache per SQC */
 +	if (adev->gfx.config.gc_l1_data_cache_size_per_sqc) {
 +		pcache_info[i].cache_size = adev->gfx.config.gc_l1_data_cache_size_per_sqc;
 +		pcache_info[i].cache_level = 1;
 +		pcache_info[i].flags = (CRAT_CACHE_FLAGS_ENABLED |
 +					CRAT_CACHE_FLAGS_DATA_CACHE |
 +					CRAT_CACHE_FLAGS_SIMD_CACHE);
 +		pcache_info[i].num_cu_shared = adev->gfx.config.gc_num_sqc_per_wgp * 2;
 +		i++;
 +	}

参考リンク