<div dir="ltr"><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Jul 24, 2023 at 9:04 PM Danilo Krummrich <<a href="mailto:dakr@redhat.com">dakr@redhat.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On 7/22/23 17:12, Faith Ekstrand wrote:<br>
> On Wed, Jul 19, 2023 at 7:15 PM Danilo Krummrich <<a href="mailto:dakr@redhat.com" target="_blank">dakr@redhat.com</a> <br>
> <mailto:<a href="mailto:dakr@redhat.com" target="_blank">dakr@redhat.com</a>>> wrote:<br>
> <br>
>     This commit provides the implementation for the new uapi motivated<br>
>     by the<br>
>     Vulkan API. It allows user mode drivers (UMDs) to:<br>
> <br>
>     1) Initialize a GPU virtual address (VA) space via the new<br>
>         DRM_IOCTL_NOUVEAU_VM_INIT ioctl for UMDs to specify the portion<br>
>     of VA<br>
>         space managed by the kernel and userspace, respectively.<br>
> <br>
>     2) Allocate and free a VA space region as well as bind and unbind memory<br>
>         to the GPUs VA space via the new DRM_IOCTL_NOUVEAU_VM_BIND ioctl.<br>
>         UMDs can request the named operations to be processed either<br>
>         synchronously or asynchronously. It supports DRM syncobjs<br>
>         (incl. timelines) as synchronization mechanism. The management<br>
>     of the<br>
>         GPU VA mappings is implemented with the DRM GPU VA manager.<br>
> <br>
>     3) Execute push buffers with the new DRM_IOCTL_NOUVEAU_EXEC ioctl. The<br>
>         execution happens asynchronously. It supports DRM syncobj (incl.<br>
>         timelines) as synchronization mechanism. DRM GEM object locking is<br>
>         handled with drm_exec.<br>
> <br>
>     Both, DRM_IOCTL_NOUVEAU_VM_BIND and DRM_IOCTL_NOUVEAU_EXEC, use the DRM<br>
>     GPU scheduler for the asynchronous paths.<br>
> <br>
> <br>
> IDK where the best place to talk about this is but this seems as good as <br>
> any.<br>
> <br>
> I've been looking into why the Vulkan CTS runs about 2x slower for me on <br>
> the new UAPI and I created a little benchmark to facilitate testing:<br>
> <br>
> <a href="https://gitlab.freedesktop.org/mesa/crucible/-/merge_requests/141" rel="noreferrer" target="_blank">https://gitlab.freedesktop.org/mesa/crucible/-/merge_requests/141</a> <br>
> <<a href="https://gitlab.freedesktop.org/mesa/crucible/-/merge_requests/141" rel="noreferrer" target="_blank">https://gitlab.freedesktop.org/mesa/crucible/-/merge_requests/141</a>><br>
> <br>
> The test, roughly, does the following:<br>
>   1. Allocates and binds 1000 BOs<br>
>   2. Constructs a pushbuf that executes a no-op compute shader.<br>
>   3. Does a single EXEC/wait combo to warm up the kernel<br>
>   4. Loops 10,000 times, doing SYNCOBJ_RESET (fast), EXEC, and then <br>
> SYNCOBJ_WAIT and times the loop<br>
> <br>
> Of course, there's a bit of userspace driver overhead but that's <br>
> negledgable.<br>
> <br>
> If you drop the top patch which allocates 1k buffers, the submit time on <br>
> the old uAPI is 54 us/exec vs. 66 us/exec on the new UAPI. This includes <br>
> the time to do a SYNCOBJ_RESET (fast), EXEC, and SYNCOBJ_WAIT.The Intel <br>
> driver, by comparison, is 33us/exec so it's not syncobj overhead. This <br>
> is a bit concerning (you'd think the new thing would be faster) but what <br>
> really has me concerned is the 1k buffer case.<br>
> <br>
> If you include the top patch in the crucible MR, it allocates 1000 BOs <br>
> and VM_BINDs them. All the binding is done before the warmup EXEC. <br>
> Suddenly, the submit time jumps to 257 us/exec with the new UAPI. The <br>
> old UAPI is much worse (1134 us/exec) but that's not the point. Once <br>
> we've done the first EXEC and created our VM bindings, the cost per EXEC <br>
> shouldn't change at all based on the number of BOs bound.  Part of the <br>
> point of VM_BIND is to get all that binding logic and BO walking off the <br>
> EXEC path.<br>
> <br>
> Normally, I wouldn't be too worried about a little performance problem <br>
> like this. This is the first implementation and we can improve it later. <br>
> I get that. However, I suspect the solution to this problem involves <br>
> more UAPI and I want to make sure we have it all before we call this all <br>
> done and dusted and land it.<br>
> <br>
> The way AMD solves this problem as well as the new Xe driver for Intel <br>
> is to have a concept of internal vs. external BOs. Basically, there's an <br>
> INTERNAL bit specified somewhere in BO creation that has a few userspace <br>
> implications:<br>
>   1. In the Xe world where VMs are objects, INTERNAL BOs are assigned a <br>
> VM on creation and can never be bound to any other VM.<br>
>   2. Any attempt to export an INTERNAL BO via prime or a similar <br>
> mechanism will fail with -EINVAL (I think?).<br>
> <br>
> Inside the kernel driver, all the internal BOs on a VM (or DRM file in <br>
> the case of nouveau/AMD since they don't have VM objects) share a single <br>
> dma_resv which allows you to avoid having to walk lists of BOs and take <br>
> locks on every exec. Instead, you can just look at the fences on the <br>
> dma_resv for the VM. There's still a BO list associated with the VM for <br>
> external BOs but, in most Vulkan applications, there are less than a <br>
> half dozen external BOs total.  Meanwhile, the hundreds or thousands of <br>
> BOs used entirely internally to the application basically count as one <br>
> BO when it comes to locking overhead.<br>
<br>
I am aware of that and I have some WIP patches [1] to generalize a <br>
common dma-resv within the GPUVA manager which basically represents a <br>
GPU-VM. It also keeps track of external GEM objects and evicted objects, <br>
such that on EXEC we only need to validate objects needing validation, <br>
rather than all of them. Hence, it should be faster than with Daves <br>
patch having a common dma-resv only.<br>
<br>
In [1] I also picked up Daves code to allow for noop jobs to be <br>
submitted as well as the NOUVEAU_GEM_DOMAIN_NO_SHARE flag.<br>
<br>
This seems to work fine with yours and Daves latest mesa work <br>
(670c301a9845a3fc795fd48a1e6714e75b388245).<br>
<br>
Your crucible bench.submit-latency test goes down to 51us on my machine <br>
with those patches.<br>
<br>
I am unsure though, if we should aim for a common solution within the <br>
GPUVA manager directly or if we should do it driver specific in a first <br>
shot. I discussed this patch with Matt and I know that XE looks for <br>
having a generalized solution as well. However, it surely needs some <br>
more care and polish and feedback from other drivers perspective.<br>
<br>
[1] <br>
<a href="https://gitlab.freedesktop.org/nouvelles/kernel/-/tree/new-uapi-drm-next-vm-resv" rel="noreferrer" target="_blank">https://gitlab.freedesktop.org/nouvelles/kernel/-/tree/new-uapi-drm-next-vm-resv</a><br>
<br>
> <br>
> I'm not actually trying to dictate kernel driver design here. If one <br>
> dma_resv doesn't work, fine. I don't care as long as EXEC scales. <br>
> However, given that the solution in all the other drivers involves a BO <br>
> create flag nouveau doesn't have, we need to either add that or prove <br>
> that we can get EXEC to scale without it.<br>
<br>
 From #nouveau:<br>
<br>
<gfxstrand> CTSing now<br>
<gfxstrand> It looks like it's still going to take 1.5 hours.<br>
<br>
I may have an idea what could be the issue, let me explain.<br>
<br>
Currently, there is a single drm_gpu_scheduler having a drm_sched_entity <br>
per client (for VM_BIND jobs) and a drm_sched_entity per channel (for <br>
EXEC jobs).<br>
<br>
For VM_BIND jobs the corresponding PT[E]s are allocated before the job <br>
is pushed to the corresponding drm_sched_entity. The PT[E]s are freed by <br>
the schedulers free() callback pushing work to a single threaded <br>
workqueue doing the actual free. (We can't do it in the free() callback <br>
directly, since to free PT[E]s we need to hold a mutex we also need to <br>
hold while allocating them.)<br>
<br>
Because of how the page table handling in Nouveau is implemented <br>
currently there are some ordering restrictions when it comes to <br>
allocating and freeing PT[E]s. For instance, we can't allocate PT[E]s <br>
for sparse regions before the PT[E]s of previously removed memory backed <br>
mappings *within the same address range* aren't freed. The same applies <br>
vice versa and for sparse mapping replacing sparse mapping. For memory <br>
backed mappings (also for those within sparse regions) we do *not* have <br>
such ordering requirements.<br>
<br>
So, let's assume userspace removes a sparse region A[0x0, 0x8000000] and <br>
asks for a couple of new memory backed mappings within or crossing this <br>
range; the kernel needs to wait for A not only to be unmapped, but also <br>
the backing PT[E]s to be freed before it can even allocate the PT[E]s <br>
for the new memory backed mappings.<br>
<br>
Now, let's have a look what the gpu schedulers main loop does. Before <br>
picking the next entity to schedule a job for, it tries to fetch the <br>
first job from the pending_list and checks whether its dma-fence is <br>
signaled already and whether the job can be cleaned up. Subsequent jobs <br>
on the pending_list are not taken into consideration. Hence, it might <br>
well be that the first job on the pending_list isn't signaled yet, but <br>
subsequent jobs are and hence *could* be cleaned up.<br>
<br>
Normally, this shouldn't be a problem, since we wouldn't really care <br>
*when* resources are cleaned up as long as they are eventually. However, <br>
with the ordering restrictions the page table handling gives us we might <br>
actually care about the "when".<br>
<br>
For instance, it could happen that the first job on the pending list is <br>
a rather long running EXEC job (1) scheduled from client A on some <br>
channel. The next job on the pending list could be a VM_BIND job (2) <br>
from client B removing a sparse region, which is finished already but is <br>
blocked to be cleaned up until the EXEC job (1) from client A is <br>
finished and cleaned up. Now, a subsequent VM_BIND job (3) from client B <br>
creating a new memory backed mapping in the same address range as the <br>
sparse region removed by job (2) would need to wait for (2) to be <br>
cleaned up. Ultimately, we can expect client B to submit an EXEC job <br>
that needs to wait for the corresponding mappings to be created, namely <br>
the VM_BIND job (3).<br>
<br>
Clearly in order to address this we need to rework the page table <br>
handling in Nouveau to get rid of those ordering restrictions.<br>
<br>
Temporarily, we could also try to run a secondary drm_gpu_scheduler <br>
instance, one for VM_BINDs and one for EXECs maybe...<br>
<br>
However, I would not expect this to be an issue in real applications, <br>
especially if mesa takes a little care not to re-use certain address <br>
space areas right away to avoid running into such wait conditions.<br>
<br>
For parallel VK CTS runs I could imagine that we run into such cases <br>
from time to time though.<br></blockquote><div><br></div><div>Thanks for the detailed write-up! That would definitely explain it. If I remember, I'll try to do a single-threaded run or two. If your theory is correct, there should be no real perf difference when running single-threaded. Those runs will take a long time, though, so I'll have to run them over night. I'll let you know in a few days once I have the results.</div><div><br></div><div>If this theory holds, then I'm not concerned about the performance of the API itself. It would still be good to see if we can find a way to reduce the cross-process drag in the implementation but that's a perf optimization we can do later.</div><div><br></div><div>Does it actually matter? Yes, it kinda does. No, it probably doesn't matter for games because you're typically only running one game at a time. From a development PoV, however, if it makes CI take longer then that slows down development and that's not good for the users, either.</div><div><br></div><div>~Faith<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
- Danilo<br>
<br>
> <br>
> ~Faith<br>
> <br>
<br>
</blockquote></div></div>