[PATCH 4/4] drm/xe: Add migrate performance kunit test

Matthew Brost matthew.brost at intel.com
Thu May 8 06:49:04 UTC 2025


On Fri, May 02, 2025 at 11:35:13AM +0200, Maarten Lankhorst wrote:
> Ever since we added migration, we didn't have concrete numbers on how
> performant our implementation is. We are currently writing pagetables,
> flushing, then writing data and flushing again. Lets see what happens
> when we fix this.
> 

I had the same idea—but in the context of SVM. Using 2M DMA mappings
(out of context here) or switching the paging engine to ULLS (in
context) was what interested me. This is a good start to build on.

> Signed-off-by: Maarten Lankhorst <dev at lankhorst.se>
> ---
>  drivers/gpu/drm/xe/tests/xe_migrate.c | 235 ++++++++++++++++++++++++++
>  1 file changed, 235 insertions(+)
> 
> diff --git a/drivers/gpu/drm/xe/tests/xe_migrate.c b/drivers/gpu/drm/xe/tests/xe_migrate.c
> index 12a3318b1a5d5..d8992ba71281b 100644
> --- a/drivers/gpu/drm/xe/tests/xe_migrate.c
> +++ b/drivers/gpu/drm/xe/tests/xe_migrate.c
> @@ -661,6 +661,128 @@ static struct xe_bo *migratable_bo_create_pin_map(struct kunit *test, struct xe_
>  	return bo;
>  }
>  
> +static bool handle_vm_fence(struct xe_device *xe, struct dma_fence *fence,
> +			    struct kunit *test)
> +{
> +	bool ret;
> +
> +	if (!fence)
> +		return false;
> +
> +	ret = sanity_fence_failed(xe, fence, "VM_BIND", test);
> +	if (!IS_ERR(fence))
> +		dma_fence_put(fence);
> +	return ret;
> +}
> +
> +#define BS_BO2BO MAX_PREEMPTDISABLE_TRANSFER
> +
> +static void write_bo2bo_copy(struct xe_tile *tile, struct xe_bo *batch,
> +			     u64 src_ofs, u64 dst_ofs, u64 size)
> +{
> +	struct xe_bb bb = {
> +		.cs = batch->vmap.vaddr,
> +		.len = 0,
> +	};
> +
> +	while (size) {
> +		u64 block = min(size, BS_BO2BO);
> +
> +		emit_copy(tile->primary_gt, &bb, src_ofs, dst_ofs, block, SZ_32K);
> +
> +		src_ofs += block;
> +		dst_ofs += block;
> +		size -= block;
> +	}
> +	bb.cs[bb.len++] = MI_BATCH_BUFFER_END;
> +	xe_gt_assert(tile->primary_gt, bb.len * 4 < batch->size);
> +}
> +
> +static void compare_bo2bo(struct kunit *test,
> +			  struct xe_tile *tile,
> +			  struct xe_vm *vm,
> +			  struct xe_exec_queue *q,
> +			  struct xe_bo *batch_bo, u64 batch_ofs,
> +			  struct xe_bo *src, u64 src_ofs,
> +			  struct xe_bo *dst, u64 dst_ofs,
> +			  const char *description)
> +{
> +	struct xe_device *xe = tile->xe;
> +	s64 t_migrate = 0, t_job = 0;
> +	int iter = 4;
> +
> +	for (int n = 0; n < iter; n++) {
> +		struct xe_sched_job *job;
> +		struct dma_fence *fence;
> +		u64 delta;
> +		ktime_t start;
> +
> +		start = ktime_get();
> +		job = xe_sched_job_create(q, &batch_ofs);
> +
> +		write_bo2bo_copy(tile, batch_bo, src_ofs, dst_ofs, src->size);
> +
> +		xe_sched_job_arm(job);
> +		fence = dma_fence_get(&job->drm.s_fence->finished);
> +		xe_sched_job_push(job);
> +
> +		sanity_fence_failed(xe, fence, description, test);
> +		if (IS_ERR(fence))
> +			return;
> +
> +		dma_fence_put(fence);
> +
> +		delta = ktime_to_us(ktime_sub(ktime_get(), start));
> +		drm_dbg(&xe->drm, "[%i] Job (%s) copy of %lu buffer took %llu µs\n", n, description, src->size >> 20, delta);

Can we calculate the bandwidth in addition to time? The same applies to
all debug statements.

Also, I think we should test small sizes too—e.g., less than 2M—to see
how small copies affect bandwidth. So >> 20 wouldn't work? How about
"npages %lu buffer", using src->size >> 12 (or a defined constant)?

> +		t_job += delta;
> +
> +		start = ktime_get();
> +		fence = xe_migrate_copy(tile->migrate, src, dst, src->ttm.resource, dst->ttm.resource, false);
> +		sanity_fence_failed(xe, fence, description, test);
> +		if (IS_ERR(fence))
> +			return;
> +
> +		dma_fence_put(fence);
> +		delta = ktime_to_us(ktime_sub(ktime_get(), start));
> +		drm_dbg(&xe->drm, "[%i] Job (%s) migration of %lu buffer took %llu µs\n", n, description, src->size >> 20, delta);
> +		t_migrate += delta;
> +
> +		cond_resched();
> +	}
> +
> +	drm_info(&xe->drm, "%s %lu MB buffer took on average %llu us for copy, %llu us for migrate\n", description, src->size >> 20, div_u64(t_job, iter), div_u64(t_migrate, iter));
> +}
> +
> +static void performance_tile(struct xe_device *xe,
> +			     struct xe_tile *tile,
> +			     struct kunit *test,
> +			     struct xe_vm *vm,
> +			     struct xe_exec_queue *q,
> +			     struct xe_bo *batch_bo, u64 batch_ofs,
> +			     struct xe_bo *sys_bo, u64 sys_ofs,
> +			     struct xe_bo *vram_bo, u64 vram_ofs,
> +			     struct xe_bo *alt_bo, u64 alt_ofs)
> +{
> +	drm_dbg(&xe->drm, "Using %u block size for copy, %u for migrate\n",
> +		BS_BO2BO, MAX_PREEMPTDISABLE_TRANSFER);
> +
> +	compare_bo2bo(test, tile, vm, q, batch_bo, batch_ofs,
> +		      sys_bo, sys_ofs, vram_bo, vram_ofs,
> +		      "sysmem -> vram");
> +
> +	compare_bo2bo(test, tile, vm, q, batch_bo, batch_ofs,
> +		      vram_bo, vram_ofs, sys_bo, sys_ofs,
> +		      "vram -> sysmem");
> +
> +	compare_bo2bo(test, tile, vm, q, batch_bo, batch_ofs,
> +		      vram_bo, vram_ofs, alt_bo, alt_ofs,
> +		      "vram -> vram");
> +
> +	compare_bo2bo(test, tile, vm, q, batch_bo, batch_ofs,
> +		      sys_bo, sys_ofs, sys_bo, sys_ofs,
> +		      "sysmem -> sysmem");
> +}
> +
>  static void bo_unpin_map_user(struct xe_bo *bo)
>  {
>  	if (!bo->vm)
> @@ -672,6 +794,116 @@ static void bo_unpin_map_user(struct xe_bo *bo)
>  	xe_bo_put(bo);
>  }
>  
> +static void performance_test_run_tile(struct xe_device *xe,
> +				      struct xe_tile *tile,
> +				      struct kunit *test)
> +{
> +	struct xe_bo *sys_bo, *vram_bo = NULL, *alt_bo = NULL, *batch_bo = NULL;
> +	unsigned int bo_flags = XE_BO_FLAG_VRAM_IF_DGFX(tile);
> +	struct xe_vm *vm;
> +	struct xe_exec_queue *q;
> +	struct dma_fence *fence;
> +	u64 size = ALIGN(tile->mem.vram.usable_size / 3, SZ_2M);

I think we should loop over the code below—i.e., test different sizes
and measure the bandwidth for each. That would be interesting. Make it
table-driven—4K, 64K, etc. This would also help identify bottlenecks in
the code or hardware, giving us insight into what to optimize.

> +	u64 block = ALIGN(size, SZ_1G);
> +	bool failed = true;
> +
> +	vm = xe_vm_create(xe, XE_VM_FLAG_SET_TILE_ID(tile));
> +	if (IS_ERR(vm))
> +		goto out;
> +
> +	q = xe_migrate_create_queue(tile, vm);
> +	if (IS_ERR(q))
> +		goto free_vm;
> +
> +	xe_vm_lock(vm, false);
> +
> +	sys_bo = migratable_bo_create_pin_map(test, xe, vm, size, DRM_XE_GEM_CPU_CACHING_WC, XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS);

Line wrap issue. A couple other cases too.

Matt

> +	if (IS_ERR(sys_bo))
> +		goto free_q;
> +
> +	alt_bo = migratable_bo_create_pin_map(test, xe, vm, size,
> +					      DRM_XE_GEM_CPU_CACHING_WC,
> +					      bo_flags);
> +	if (IS_ERR(alt_bo))
> +		goto free_sysbo;
> +
> +	vram_bo = migratable_bo_create_pin_map(test, xe, vm, size,
> +					       DRM_XE_GEM_CPU_CACHING_WC,
> +					       bo_flags);
> +	if (IS_ERR(vram_bo))
> +		goto free_altbo;
> +
> +	batch_bo = migratable_bo_create_pin_map(test, xe, vm, SZ_2M, DRM_XE_GEM_CPU_CACHING_WC, XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS);
> +	if (IS_ERR(batch_bo))
> +		goto free_vrambo;
> +
> +	xe_vm_unlock(vm);
> +
> +	failed = false;
> +
> +	fence = xe_vm_bind_kernel_bo(vm, batch_bo, NULL, SZ_2M, XE_CACHE_WT);
> +	failed |= handle_vm_fence(xe, fence, test);
> +
> +	fence = xe_vm_bind_kernel_bo(vm, sys_bo, NULL, block, XE_CACHE_WT);
> +	failed |= handle_vm_fence(xe, fence, test);
> +
> +	fence = xe_vm_bind_kernel_bo(vm, vram_bo, NULL, 2 * block, XE_CACHE_NONE);
> +	failed |= handle_vm_fence(xe, fence, test);
> +
> +	fence = xe_vm_bind_kernel_bo(vm, alt_bo, NULL, 3 * block, XE_CACHE_NONE);
> +	failed |= handle_vm_fence(xe, fence, test);
> +	if (failed)
> +		goto free_batchbo;
> +
> +	xe_vm_lock(vm, false);
> +
> +	performance_tile(xe, tile, test, vm, q,
> +			 batch_bo, SZ_2M, sys_bo, block,
> +			 vram_bo, 2 * block, alt_bo, 3 * block);
> +
> +free_batchbo:
> +	bo_unpin_map_user(batch_bo);
> +free_vrambo:
> +	bo_unpin_map_user(vram_bo);
> +free_altbo:
> +	bo_unpin_map_user(alt_bo);
> +free_sysbo:
> +	bo_unpin_map_user(sys_bo);
> +free_q:
> +	xe_vm_unlock(vm);
> +	xe_exec_queue_put(q);
> +free_vm:
> +	xe_vm_close_and_put(vm);
> +out:
> +	if (failed)
> +		KUNIT_FAIL(test, "Test setup failed\n");
> +}
> +
> +static void xe_migrate_performance_kunit(struct kunit *test)
> +{
> +	struct xe_device *xe = test->priv;
> +	struct xe_tile *tile;
> +	int id;
> +
> +	if (!IS_DGFX(xe)) {
> +		kunit_skip(test, "test requires VRAM\n");
> +		return;
> +	}
> +
> +	xe_pm_runtime_get(xe);
> +
> +	for_each_tile(tile, xe, id) {
> +		if (tile->mem.vram.io_size < tile->mem.vram.usable_size) {
> +			kunit_skip(test, "Small bar device.\n");
> +			break;
> +		}
> +
> +		performance_test_run_tile(xe, tile, test);
> +	}
> +
> +	xe_pm_runtime_put(xe);
> +}
> +
>  static void validate_ccs_test_run_tile(struct xe_device *xe, struct xe_tile *tile,
>  				       struct kunit *test)
>  {
> @@ -740,6 +972,9 @@ static void xe_validate_ccs_kunit(struct kunit *test)
>  
>  static struct kunit_case xe_migrate_tests[] = {
>  	KUNIT_CASE_PARAM(xe_migrate_sanity_kunit, xe_pci_live_device_gen_param),
> +	KUNIT_CASE_PARAM_ATTR(xe_migrate_performance_kunit,
> +			      xe_pci_live_device_gen_param,
> +			      {.speed = KUNIT_SPEED_SLOW}),
>  	KUNIT_CASE_PARAM(xe_validate_ccs_kunit, xe_pci_live_device_gen_param),
>  	{}
>  };
> -- 
> 2.45.2
> 


More information about the Intel-xe mailing list