I see that the error does not appear if one moves the generator forward functions out of forward and into backward like so,
 def forward(self):
        """Run forward pass. This will be called by both functions <optimize_parameters> and <test>."""
        #self.output = self.netG(self.data)  # generate output image given the input data_A
        self.noise = torch.randn(self.opt.batch_size, self.opt.input_nc, 1, 1, device=self.device)
 def backward_D(self):
        """Calculate GAN loss for the discriminator"""

        # real
        pred_real = self.netD(self.real)
        self.loss_D_real = self.criterionGAN(pred_real, True)

        # fake
        fake = self.netG(self.noise)
        pred_fake = self.netD(fake.detach())
        self.loss_D_fake = self.criterionGAN(pred_fake, False)

        # combine and calculate gradients
        self.loss_D = self.loss_D_fake + self.loss_D_real
        self.loss_D.backward()

    def backward_G(self):
        """Calculate losses, gradients, and update network weights; called in every training iteration"""
        # caculate the intermediate results if necessary; here self.output has been computed during function <forward>
        # calculate loss given the input and intermediate results
        fake = self.netG(self.noise)
        pred_fake = self.netD(fake)
        self.loss_G = self.criterionGAN(pred_fake, True)  # fake labels are real for Generator
        self.loss_G.backward()       # calculate gradients of network G w.r.t. loss_G


Or when one reverses the order of updating models (i.e., generator first), like so
def optimize_parameters(self):
        """Update network weights; it will be called in every training iteration."""
        self.forward()               # first call forward to calculate intermediate results

        # update G
        self.set_requires_grad(self.netD, False)  # D requires no gradients when optimizing G
        self.optimizer_G.zero_grad()        # set G's gradients to zero
        self.backward_G()                   # calculate graidents for G
        self.optimizer_G.step()             # udpate G's weights
        
        # update D
        self.set_requires_grad(self.netD, True)  # enable backprop for D
        self.optimizer_D.zero_grad()     # set D's gradients to zero
        self.backward_D()                # calculate gradients for D
        self.optimizer_D.step()          # update D's weights