upon reading the docs, the answer to the second question is clear- if pin_memory is set to false, then setting non_blocking = true has no effect.
Looking at the code for the data loader, setting pin_memory = true sets up an additional thread which reads from the output queue of the worker threads and puts the batch into pinned memory and the pinned memory pointer into another queue, which is then queried during the next call.
Also, I believe with asynchronous data transfer, the processing time includes the data transfer time as well. When I put a cuda.synchronize() after the .cuda() calls, now the transfer time is non-negligible and the processing time is lower by the same amount. The sum adds up to the processing time with synchronous data transfer.