We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[bug] CSIS error when parameters are not fully used
### Issue Description
For `pyro.infer.csis`
If some parameters are not used to produce the outputs, the gradients can be None. In this case, an error will be raised as following
```/usr/local/lib/python3.7/dist-packages/pyro/infer/csis.py in loss_and_grads(self, grads, batch, *args, **kwargs)
    114                 guide_grads = torch.autograd.grad(particle_loss, guide_params, allow_unused=True)
    115                 for guide_grad, guide_param in zip(guide_grads, guide_params):
--> 116                     guide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad
    117 
    118             loss += torch_item(particle_loss)

TypeError: unsupported operand type(s) for +: 'Tensor' and 'NoneType'
```
I resolved this issue locally by checking None before updating gradients
```
if guide_grad is None:
    continue
guide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad
```

### Environment
 - Python version: 3.7
 - PyTorch version: 1.8.1+cu101
 - Pyro version: 1.5.1

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pyro/infer/csis.py`
Content:
```
1 # Copyright (c) 2017-2019 Uber Technologies, Inc.
2 # SPDX-License-Identifier: Apache-2.0
3 
4 import itertools
5 
6 import torch
7 
8 import pyro
9 import pyro.poutine as poutine
10 from pyro.infer.importance import Importance
11 from pyro.infer.util import torch_item
12 from pyro.poutine.util import prune_subsample_sites
13 from pyro.util import check_model_guide_match, warn_if_nan
14 
15 
16 class CSIS(Importance):
17     """
18     Compiled Sequential Importance Sampling, allowing compilation of a guide
19     program to minimise KL(model posterior || guide), and inference with
20     importance sampling.
21 
22     **Reference**
23     "Inference Compilation and Universal Probabilistic Programming" `pdf https://arxiv.org/pdf/1610.09900.pdf`
24 
25     :param model: probabilistic model defined as a function. Must accept a
26         keyword argument named `observations`, in which observed values are
27         passed as, with the names of nodes as the keys.
28     :param guide: guide function which is used as an approximate posterior. Must
29         also accept `observations` as keyword argument.
30     :param optim: a Pyro optimizer
31     :type optim: pyro.optim.PyroOptim
32     :param num_inference_samples: The number of importance-weighted samples to
33         draw during inference.
34     :param training_batch_size: Number of samples to use to approximate the loss
35         before each gradient descent step during training.
36     :param validation_batch_size: Number of samples to use for calculating
37         validation loss (will only be used if `.validation_loss` is called).
38     """
39     def __init__(self,
40                  model,
41                  guide,
42                  optim,
43                  num_inference_samples=10,
44                  training_batch_size=10,
45                  validation_batch_size=20):
46         super().__init__(model, guide, num_inference_samples)
47         self.model = model
48         self.guide = guide
49         self.optim = optim
50         self.training_batch_size = training_batch_size
51         self.validation_batch_size = validation_batch_size
52         self.validation_batch = None
53 
54     def set_validation_batch(self, *args, **kwargs):
55         """
56         Samples a batch of model traces and stores it as an object property.
57 
58         Arguments are passed directly to model.
59         """
60         self.validation_batch = [self._sample_from_joint(*args, **kwargs)
61                                  for _ in range(self.validation_batch_size)]
62 
63     def step(self, *args, **kwargs):
64         """
65         :returns: estimate of the loss
66         :rtype: float
67 
68         Take a gradient step on the loss function. Arguments are passed to the
69         model and guide.
70         """
71         with poutine.trace(param_only=True) as param_capture:
72             loss = self.loss_and_grads(True, None, *args, **kwargs)
73 
74         params = set(site["value"].unconstrained()
75                      for site in param_capture.trace.nodes.values()
76                      if site["value"].grad is not None)
77 
78         self.optim(params)
79 
80         pyro.infer.util.zero_grads(params)
81 
82         return torch_item(loss)
83 
84     def loss_and_grads(self, grads, batch, *args, **kwargs):
85         """
86         :returns: an estimate of the loss (expectation over p(x, y) of
87             -log q(x, y) ) - where p is the model and q is the guide
88         :rtype: float
89 
90         If a batch is provided, the loss is estimated using these traces
91         Otherwise, a fresh batch is generated from the model.
92 
93         If grads is True, will also call `backward` on loss.
94 
95         `args` and `kwargs` are passed to the model and guide.
96         """
97         if batch is None:
98             batch = (self._sample_from_joint(*args, **kwargs)
99                      for _ in range(self.training_batch_size))
100             batch_size = self.training_batch_size
101         else:
102             batch_size = len(batch)
103 
104         loss = 0
105         for model_trace in batch:
106             with poutine.trace(param_only=True) as particle_param_capture:
107                 guide_trace = self._get_matched_trace(model_trace, *args, **kwargs)
108             particle_loss = self._differentiable_loss_particle(guide_trace)
109             particle_loss /= batch_size
110 
111             if grads:
112                 guide_params = set(site["value"].unconstrained()
113                                    for site in particle_param_capture.trace.nodes.values())
114                 guide_grads = torch.autograd.grad(particle_loss, guide_params, allow_unused=True)
115                 for guide_grad, guide_param in zip(guide_grads, guide_params):
116                     guide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad
117 
118             loss += torch_item(particle_loss)
119 
120         warn_if_nan(loss, "loss")
121         return loss
122 
123     def _differentiable_loss_particle(self, guide_trace):
124         return -guide_trace.log_prob_sum()
125 
126     def validation_loss(self, *args, **kwargs):
127         """
128         :returns: loss estimated using validation batch
129         :rtype: float
130 
131         Calculates loss on validation batch. If no validation batch is set,
132         will set one by calling `set_validation_batch`. Can be used to track
133         the loss in a less noisy way during training.
134 
135         Arguments are passed to the model and guide.
136         """
137         if self.validation_batch is None:
138             self.set_validation_batch(*args, **kwargs)
139 
140         return self.loss_and_grads(False, self.validation_batch, *args, **kwargs)
141 
142     def _get_matched_trace(self, model_trace, *args, **kwargs):
143         """
144         :param model_trace: a trace from the model
145         :type model_trace: pyro.poutine.trace_struct.Trace
146         :returns: guide trace with sampled values matched to model_trace
147         :rtype: pyro.poutine.trace_struct.Trace
148 
149         Returns a guide trace with values at sample and observe statements
150         matched to those in model_trace.
151 
152         `args` and `kwargs` are passed to the guide.
153         """
154         kwargs["observations"] = {}
155         for node in itertools.chain(model_trace.stochastic_nodes, model_trace.observation_nodes):
156             if "was_observed" in model_trace.nodes[node]["infer"]:
157                 model_trace.nodes[node]["is_observed"] = True
158                 kwargs["observations"][node] = model_trace.nodes[node]["value"]
159 
160         guide_trace = poutine.trace(poutine.replay(self.guide,
161                                                    model_trace)
162                                     ).get_trace(*args, **kwargs)
163 
164         check_model_guide_match(model_trace, guide_trace)
165         guide_trace = prune_subsample_sites(guide_trace)
166 
167         return guide_trace
168 
169     def _sample_from_joint(self, *args, **kwargs):
170         """
171         :returns: a sample from the joint distribution over unobserved and
172             observed variables
173         :rtype: pyro.poutine.trace_struct.Trace
174 
175         Returns a trace of the model without conditioning on any observations.
176 
177         Arguments are passed directly to the model.
178         """
179         unconditioned_model = pyro.poutine.uncondition(self.model)
180         return poutine.trace(unconditioned_model).get_trace(*args, **kwargs)
181
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pyro/infer/csis.py b/pyro/infer/csis.py
--- a/pyro/infer/csis.py
+++ b/pyro/infer/csis.py
@@ -113,6 +113,8 @@
                                    for site in particle_param_capture.trace.nodes.values())
                 guide_grads = torch.autograd.grad(particle_loss, guide_params, allow_unused=True)
                 for guide_grad, guide_param in zip(guide_grads, guide_params):
+                    if guide_grad is None:
+                        continue
                     guide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad
 
             loss += torch_item(particle_loss)

{"golden_diff": "diff --git a/pyro/infer/csis.py b/pyro/infer/csis.py\n--- a/pyro/infer/csis.py\n+++ b/pyro/infer/csis.py\n@@ -113,6 +113,8 @@\n                                    for site in particle_param_capture.trace.nodes.values())\n                 guide_grads = torch.autograd.grad(particle_loss, guide_params, allow_unused=True)\n                 for guide_grad, guide_param in zip(guide_grads, guide_params):\n+                    if guide_grad is None:\n+                        continue\n                     guide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad\n \n             loss += torch_item(particle_loss)\n", "issue": "[bug] CSIS error when parameters are not fully used\n### Issue Description\r\nFor `pyro.infer.csis`\r\nIf some parameters are not used to produce the outputs, the gradients can be None. In this case, an error will be raised as following\r\n```/usr/local/lib/python3.7/dist-packages/pyro/infer/csis.py in loss_and_grads(self, grads, batch, *args, **kwargs)\r\n    114                 guide_grads = torch.autograd.grad(particle_loss, guide_params, allow_unused=True)\r\n    115                 for guide_grad, guide_param in zip(guide_grads, guide_params):\r\n--> 116                     guide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad\r\n    117 \r\n    118             loss += torch_item(particle_loss)\r\n\r\nTypeError: unsupported operand type(s) for +: 'Tensor' and 'NoneType'\r\n```\r\nI resolved this issue locally by checking None before updating gradients\r\n```\r\nif guide_grad is None:\r\n    continue\r\nguide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad\r\n```\r\n\r\n### Environment\r\n - Python version: 3.7\r\n - PyTorch version: 1.8.1+cu101\r\n - Pyro version: 1.5.1\n", "before_files": [{"content": "# Copyright (c) 2017-2019 Uber Technologies, Inc.\n# SPDX-License-Identifier: Apache-2.0\n\nimport itertools\n\nimport torch\n\nimport pyro\nimport pyro.poutine as poutine\nfrom pyro.infer.importance import Importance\nfrom pyro.infer.util import torch_item\nfrom pyro.poutine.util import prune_subsample_sites\nfrom pyro.util import check_model_guide_match, warn_if_nan\n\n\nclass CSIS(Importance):\n    \"\"\"\n    Compiled Sequential Importance Sampling, allowing compilation of a guide\n    program to minimise KL(model posterior || guide), and inference with\n    importance sampling.\n\n    **Reference**\n    \"Inference Compilation and Universal Probabilistic Programming\" `pdf https://arxiv.org/pdf/1610.09900.pdf`\n\n    :param model: probabilistic model defined as a function. Must accept a\n        keyword argument named `observations`, in which observed values are\n        passed as, with the names of nodes as the keys.\n    :param guide: guide function which is used as an approximate posterior. Must\n        also accept `observations` as keyword argument.\n    :param optim: a Pyro optimizer\n    :type optim: pyro.optim.PyroOptim\n    :param num_inference_samples: The number of importance-weighted samples to\n        draw during inference.\n    :param training_batch_size: Number of samples to use to approximate the loss\n        before each gradient descent step during training.\n    :param validation_batch_size: Number of samples to use for calculating\n        validation loss (will only be used if `.validation_loss` is called).\n    \"\"\"\n    def __init__(self,\n                 model,\n                 guide,\n                 optim,\n                 num_inference_samples=10,\n                 training_batch_size=10,\n                 validation_batch_size=20):\n        super().__init__(model, guide, num_inference_samples)\n        self.model = model\n        self.guide = guide\n        self.optim = optim\n        self.training_batch_size = training_batch_size\n        self.validation_batch_size = validation_batch_size\n        self.validation_batch = None\n\n    def set_validation_batch(self, *args, **kwargs):\n        \"\"\"\n        Samples a batch of model traces and stores it as an object property.\n\n        Arguments are passed directly to model.\n        \"\"\"\n        self.validation_batch = [self._sample_from_joint(*args, **kwargs)\n                                 for _ in range(self.validation_batch_size)]\n\n    def step(self, *args, **kwargs):\n        \"\"\"\n        :returns: estimate of the loss\n        :rtype: float\n\n        Take a gradient step on the loss function. Arguments are passed to the\n        model and guide.\n        \"\"\"\n        with poutine.trace(param_only=True) as param_capture:\n            loss = self.loss_and_grads(True, None, *args, **kwargs)\n\n        params = set(site[\"value\"].unconstrained()\n                     for site in param_capture.trace.nodes.values()\n                     if site[\"value\"].grad is not None)\n\n        self.optim(params)\n\n        pyro.infer.util.zero_grads(params)\n\n        return torch_item(loss)\n\n    def loss_and_grads(self, grads, batch, *args, **kwargs):\n        \"\"\"\n        :returns: an estimate of the loss (expectation over p(x, y) of\n            -log q(x, y) ) - where p is the model and q is the guide\n        :rtype: float\n\n        If a batch is provided, the loss is estimated using these traces\n        Otherwise, a fresh batch is generated from the model.\n\n        If grads is True, will also call `backward` on loss.\n\n        `args` and `kwargs` are passed to the model and guide.\n        \"\"\"\n        if batch is None:\n            batch = (self._sample_from_joint(*args, **kwargs)\n                     for _ in range(self.training_batch_size))\n            batch_size = self.training_batch_size\n        else:\n            batch_size = len(batch)\n\n        loss = 0\n        for model_trace in batch:\n            with poutine.trace(param_only=True) as particle_param_capture:\n                guide_trace = self._get_matched_trace(model_trace, *args, **kwargs)\n            particle_loss = self._differentiable_loss_particle(guide_trace)\n            particle_loss /= batch_size\n\n            if grads:\n                guide_params = set(site[\"value\"].unconstrained()\n                                   for site in particle_param_capture.trace.nodes.values())\n                guide_grads = torch.autograd.grad(particle_loss, guide_params, allow_unused=True)\n                for guide_grad, guide_param in zip(guide_grads, guide_params):\n                    guide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad\n\n            loss += torch_item(particle_loss)\n\n        warn_if_nan(loss, \"loss\")\n        return loss\n\n    def _differentiable_loss_particle(self, guide_trace):\n        return -guide_trace.log_prob_sum()\n\n    def validation_loss(self, *args, **kwargs):\n        \"\"\"\n        :returns: loss estimated using validation batch\n        :rtype: float\n\n        Calculates loss on validation batch. If no validation batch is set,\n        will set one by calling `set_validation_batch`. Can be used to track\n        the loss in a less noisy way during training.\n\n        Arguments are passed to the model and guide.\n        \"\"\"\n        if self.validation_batch is None:\n            self.set_validation_batch(*args, **kwargs)\n\n        return self.loss_and_grads(False, self.validation_batch, *args, **kwargs)\n\n    def _get_matched_trace(self, model_trace, *args, **kwargs):\n        \"\"\"\n        :param model_trace: a trace from the model\n        :type model_trace: pyro.poutine.trace_struct.Trace\n        :returns: guide trace with sampled values matched to model_trace\n        :rtype: pyro.poutine.trace_struct.Trace\n\n        Returns a guide trace with values at sample and observe statements\n        matched to those in model_trace.\n\n        `args` and `kwargs` are passed to the guide.\n        \"\"\"\n        kwargs[\"observations\"] = {}\n        for node in itertools.chain(model_trace.stochastic_nodes, model_trace.observation_nodes):\n            if \"was_observed\" in model_trace.nodes[node][\"infer\"]:\n                model_trace.nodes[node][\"is_observed\"] = True\n                kwargs[\"observations\"][node] = model_trace.nodes[node][\"value\"]\n\n        guide_trace = poutine.trace(poutine.replay(self.guide,\n                                                   model_trace)\n                                    ).get_trace(*args, **kwargs)\n\n        check_model_guide_match(model_trace, guide_trace)\n        guide_trace = prune_subsample_sites(guide_trace)\n\n        return guide_trace\n\n    def _sample_from_joint(self, *args, **kwargs):\n        \"\"\"\n        :returns: a sample from the joint distribution over unobserved and\n            observed variables\n        :rtype: pyro.poutine.trace_struct.Trace\n\n        Returns a trace of the model without conditioning on any observations.\n\n        Arguments are passed directly to the model.\n        \"\"\"\n        unconditioned_model = pyro.poutine.uncondition(self.model)\n        return poutine.trace(unconditioned_model).get_trace(*args, **kwargs)\n", "path": "pyro/infer/csis.py"}], "after_files": [{"content": "# Copyright (c) 2017-2019 Uber Technologies, Inc.\n# SPDX-License-Identifier: Apache-2.0\n\nimport itertools\n\nimport torch\n\nimport pyro\nimport pyro.poutine as poutine\nfrom pyro.infer.importance import Importance\nfrom pyro.infer.util import torch_item\nfrom pyro.poutine.util import prune_subsample_sites\nfrom pyro.util import check_model_guide_match, warn_if_nan\n\n\nclass CSIS(Importance):\n    \"\"\"\n    Compiled Sequential Importance Sampling, allowing compilation of a guide\n    program to minimise KL(model posterior || guide), and inference with\n    importance sampling.\n\n    **Reference**\n    \"Inference Compilation and Universal Probabilistic Programming\" `pdf https://arxiv.org/pdf/1610.09900.pdf`\n\n    :param model: probabilistic model defined as a function. Must accept a\n        keyword argument named `observations`, in which observed values are\n        passed as, with the names of nodes as the keys.\n    :param guide: guide function which is used as an approximate posterior. Must\n        also accept `observations` as keyword argument.\n    :param optim: a Pyro optimizer\n    :type optim: pyro.optim.PyroOptim\n    :param num_inference_samples: The number of importance-weighted samples to\n        draw during inference.\n    :param training_batch_size: Number of samples to use to approximate the loss\n        before each gradient descent step during training.\n    :param validation_batch_size: Number of samples to use for calculating\n        validation loss (will only be used if `.validation_loss` is called).\n    \"\"\"\n    def __init__(self,\n                 model,\n                 guide,\n                 optim,\n                 num_inference_samples=10,\n                 training_batch_size=10,\n                 validation_batch_size=20):\n        super().__init__(model, guide, num_inference_samples)\n        self.model = model\n        self.guide = guide\n        self.optim = optim\n        self.training_batch_size = training_batch_size\n        self.validation_batch_size = validation_batch_size\n        self.validation_batch = None\n\n    def set_validation_batch(self, *args, **kwargs):\n        \"\"\"\n        Samples a batch of model traces and stores it as an object property.\n\n        Arguments are passed directly to model.\n        \"\"\"\n        self.validation_batch = [self._sample_from_joint(*args, **kwargs)\n                                 for _ in range(self.validation_batch_size)]\n\n    def step(self, *args, **kwargs):\n        \"\"\"\n        :returns: estimate of the loss\n        :rtype: float\n\n        Take a gradient step on the loss function. Arguments are passed to the\n        model and guide.\n        \"\"\"\n        with poutine.trace(param_only=True) as param_capture:\n            loss = self.loss_and_grads(True, None, *args, **kwargs)\n\n        params = set(site[\"value\"].unconstrained()\n                     for site in param_capture.trace.nodes.values()\n                     if site[\"value\"].grad is not None)\n\n        self.optim(params)\n\n        pyro.infer.util.zero_grads(params)\n\n        return torch_item(loss)\n\n    def loss_and_grads(self, grads, batch, *args, **kwargs):\n        \"\"\"\n        :returns: an estimate of the loss (expectation over p(x, y) of\n            -log q(x, y) ) - where p is the model and q is the guide\n        :rtype: float\n\n        If a batch is provided, the loss is estimated using these traces\n        Otherwise, a fresh batch is generated from the model.\n\n        If grads is True, will also call `backward` on loss.\n\n        `args` and `kwargs` are passed to the model and guide.\n        \"\"\"\n        if batch is None:\n            batch = (self._sample_from_joint(*args, **kwargs)\n                     for _ in range(self.training_batch_size))\n            batch_size = self.training_batch_size\n        else:\n            batch_size = len(batch)\n\n        loss = 0\n        for model_trace in batch:\n            with poutine.trace(param_only=True) as particle_param_capture:\n                guide_trace = self._get_matched_trace(model_trace, *args, **kwargs)\n            particle_loss = self._differentiable_loss_particle(guide_trace)\n            particle_loss /= batch_size\n\n            if grads:\n                guide_params = set(site[\"value\"].unconstrained()\n                                   for site in particle_param_capture.trace.nodes.values())\n                guide_grads = torch.autograd.grad(particle_loss, guide_params, allow_unused=True)\n                for guide_grad, guide_param in zip(guide_grads, guide_params):\n                    if guide_grad is None:\n                        continue\n                    guide_param.grad = guide_grad if guide_param.grad is None else guide_param.grad + guide_grad\n\n            loss += torch_item(particle_loss)\n\n        warn_if_nan(loss, \"loss\")\n        return loss\n\n    def _differentiable_loss_particle(self, guide_trace):\n        return -guide_trace.log_prob_sum()\n\n    def validation_loss(self, *args, **kwargs):\n        \"\"\"\n        :returns: loss estimated using validation batch\n        :rtype: float\n\n        Calculates loss on validation batch. If no validation batch is set,\n        will set one by calling `set_validation_batch`. Can be used to track\n        the loss in a less noisy way during training.\n\n        Arguments are passed to the model and guide.\n        \"\"\"\n        if self.validation_batch is None:\n            self.set_validation_batch(*args, **kwargs)\n\n        return self.loss_and_grads(False, self.validation_batch, *args, **kwargs)\n\n    def _get_matched_trace(self, model_trace, *args, **kwargs):\n        \"\"\"\n        :param model_trace: a trace from the model\n        :type model_trace: pyro.poutine.trace_struct.Trace\n        :returns: guide trace with sampled values matched to model_trace\n        :rtype: pyro.poutine.trace_struct.Trace\n\n        Returns a guide trace with values at sample and observe statements\n        matched to those in model_trace.\n\n        `args` and `kwargs` are passed to the guide.\n        \"\"\"\n        kwargs[\"observations\"] = {}\n        for node in itertools.chain(model_trace.stochastic_nodes, model_trace.observation_nodes):\n            if \"was_observed\" in model_trace.nodes[node][\"infer\"]:\n                model_trace.nodes[node][\"is_observed\"] = True\n                kwargs[\"observations\"][node] = model_trace.nodes[node][\"value\"]\n\n        guide_trace = poutine.trace(poutine.replay(self.guide,\n                                                   model_trace)\n                                    ).get_trace(*args, **kwargs)\n\n        check_model_guide_match(model_trace, guide_trace)\n        guide_trace = prune_subsample_sites(guide_trace)\n\n        return guide_trace\n\n    def _sample_from_joint(self, *args, **kwargs):\n        \"\"\"\n        :returns: a sample from the joint distribution over unobserved and\n            observed variables\n        :rtype: pyro.poutine.trace_struct.Trace\n\n        Returns a trace of the model without conditioning on any observations.\n\n        Arguments are passed directly to the model.\n        \"\"\"\n        unconditioned_model = pyro.poutine.uncondition(self.model)\n        return poutine.trace(unconditioned_model).get_trace(*args, **kwargs)\n", "path": "pyro/infer/csis.py"}]}