auto ts = std::chrono::system_clock::now();
cudaMemcpyAsync((void**)in_dev, in_host, 1000 * size, cudaMemcpyHostToDevice, stream_in);
cudaMemcpyAsync((void**)out_host, out_dev, 1000 * size, cudaMemcpyDeviceToHost, stream_out);
cudaStreamSynchronize(stream_in);
cudaStreamSynchronize(stream_out);
time_data.push_back(std::chrono::system_clock::now() - ts);
This is the results of a benchmark I made for my own educational purposes. Pretty simple, every 'cycle' of the program it launches parallel transfer of data and waits for those operations to be complete before taking a timestamp.
The kernel version adds a simple kernel that operates on every byte of data (also on a different stream). The trend of kernel execution time makes sense to me - my device only has so many SMs/cores and it will start taking longer once I ask for more.
What I don't understand is why the memory transfer only tests start ramping up exponentially at nearly the same data size point as the core limitations. The memory bandwidth for my device is advertised as 600 GB/s. Transferring 10 MB here takes on average ~1.5 milliseconds which isn't what napkin math would suggest given bandwidth. My expectation was that time would be nearly constant around the memory transfer latency, but that doesn't seem to be the case.
To confirm it was not my bootleg time stamp methods I ran the memory only version with NSight Compute and confirmed that going from N=1000 KB to N=10000 KB increased average async transfer time from ~80 us to around ~800 us.
What am I missing about D/H memory transfer performance? Is the key to getting good bandwidth overlapping lots of small transfers rather than large transfers or would that be worse because of limited copy engine bottlenecks?
I ran this benchmark on an RTX 3070 Ti with a pcie4 system.

