We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Use stable hashing in ps client


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `python/tflib/ps/ps_client.py`
Content:
```
1 import future
2 
3 
4 # No partition, all in the first ps
5 def no_partition(v, ps_size):
6     return [v]
7 
8 
9 # Partition v according to the name hashing.
10 def hash_partition(v, ps_size):
11     if v is None:
12         return [None for i in range(ps_size)]
13     if len(v) == 0:
14         raise ValueError('Empty input used in HashPartition.')
15     if ps_size == 1:
16         return [v]
17     if isinstance(v, dict):
18         # (name, data) dict from push
19         results = [{} for i in range(ps_size)]
20         for name, data in v.items():
21             index = hash(name) % ps_size
22             results[index][name] = data
23     elif isinstance(v, list):
24         # name list from pull
25         results = [[] for i in range(ps_size)]
26         for name in v:
27             index = hash(name) % ps_size
28             results[index].append(name)
29     else:
30         raise TypeError('Illegal v type %s, only dict or '
31                         'str list is supported.' % str(type(v)))
32     return results
33 
34 
35 # Multi-thread implementation of PSClientComm
36 class MultiThreadPSClientComm(object):
37     def __init__(self, ps):
38         self._ps = ps
39 
40     def push(self, base_step, sub_step, grads):
41         if len(grads) > 0:
42             self._ps.push(base_step, sub_step, grads)
43 
44     def pull(self, names=None, min_step=0):
45         return self._ps.pull(names=names, min_step=min_step)
46 
47 
48 # ParameterSererClient uses PSClientComm for ps data transfer.
49 class ParameterServerClient(object):
50     def __init__(self,
51                  ps_configs=None,
52                  comm_class=MultiThreadPSClientComm,
53                  partition_func=no_partition):
54         self._ps_size = 1 if partition_func == no_partition else len(ps_configs)
55         self._partition_func = partition_func
56         self._clients = [comm_class(ps_configs[i])
57                          for i in range(self._ps_size)]
58         self._base_step = [0 for _ in range(self._ps_size)]
59 
60     def push(self, sub_step=0, grads=None):
61         partition_result = self._partition_func(grads, self._ps_size)
62         # TODO: multithread optimization, one thread per ps communication.
63         for index, g in enumerate(partition_result):
64             self._clients[index].push(self._base_step[index],
65                                       sub_step, g)
66 
67     def pull(self, min_step=0, names=None):
68         pull_result = {}
69         partition_result = self._partition_func(names, self._ps_size)
70         # TODO: multithread optimization, one thread per ps communication.
71         for index, n in enumerate(partition_result):
72             ps_step, ps_vars = self._clients[index].pull(
73                 names=n, min_step=min_step)
74             self._base_step[index] = ps_step
75             pull_result.update(ps_vars)
76         return self.get_min_base_step(), pull_result
77 
78     def get_min_base_step(self):
79         return min(self._base_step)
80
```
--- 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/python/tflib/ps/ps_client.py b/python/tflib/ps/ps_client.py
--- a/python/tflib/ps/ps_client.py
+++ b/python/tflib/ps/ps_client.py
@@ -1,5 +1,4 @@
-import future
-
+from binascii import crc32
 
 # No partition, all in the first ps
 def no_partition(v, ps_size):
@@ -8,6 +7,10 @@
 
 # Partition v according to the name hashing.
 def hash_partition(v, ps_size):
+    # a simple stable string hash
+    def _hash(s):
+        return crc32(s.encode())
+
     if v is None:
         return [None for i in range(ps_size)]
     if len(v) == 0:
@@ -18,13 +21,13 @@
         # (name, data) dict from push
         results = [{} for i in range(ps_size)]
         for name, data in v.items():
-            index = hash(name) % ps_size
+            index = _hash(name) % ps_size
             results[index][name] = data
     elif isinstance(v, list):
         # name list from pull
         results = [[] for i in range(ps_size)]
         for name in v:
-            index = hash(name) % ps_size
+            index = _hash(name) % ps_size
             results[index].append(name)
     else:
         raise TypeError('Illegal v type %s, only dict or '

{"golden_diff": "diff --git a/python/tflib/ps/ps_client.py b/python/tflib/ps/ps_client.py\n--- a/python/tflib/ps/ps_client.py\n+++ b/python/tflib/ps/ps_client.py\n@@ -1,5 +1,4 @@\n-import future\n-\n+from binascii import crc32\n \n # No partition, all in the first ps\n def no_partition(v, ps_size):\n@@ -8,6 +7,10 @@\n \n # Partition v according to the name hashing.\n def hash_partition(v, ps_size):\n+    # a simple stable string hash\n+    def _hash(s):\n+        return crc32(s.encode())\n+\n     if v is None:\n         return [None for i in range(ps_size)]\n     if len(v) == 0:\n@@ -18,13 +21,13 @@\n         # (name, data) dict from push\n         results = [{} for i in range(ps_size)]\n         for name, data in v.items():\n-            index = hash(name) % ps_size\n+            index = _hash(name) % ps_size\n             results[index][name] = data\n     elif isinstance(v, list):\n         # name list from pull\n         results = [[] for i in range(ps_size)]\n         for name in v:\n-            index = hash(name) % ps_size\n+            index = _hash(name) % ps_size\n             results[index].append(name)\n     else:\n         raise TypeError('Illegal v type %s, only dict or '\n", "issue": "Use stable hashing in ps client\n\n", "before_files": [{"content": "import future\n\n\n# No partition, all in the first ps\ndef no_partition(v, ps_size):\n    return [v]\n\n\n# Partition v according to the name hashing.\ndef hash_partition(v, ps_size):\n    if v is None:\n        return [None for i in range(ps_size)]\n    if len(v) == 0:\n        raise ValueError('Empty input used in HashPartition.')\n    if ps_size == 1:\n        return [v]\n    if isinstance(v, dict):\n        # (name, data) dict from push\n        results = [{} for i in range(ps_size)]\n        for name, data in v.items():\n            index = hash(name) % ps_size\n            results[index][name] = data\n    elif isinstance(v, list):\n        # name list from pull\n        results = [[] for i in range(ps_size)]\n        for name in v:\n            index = hash(name) % ps_size\n            results[index].append(name)\n    else:\n        raise TypeError('Illegal v type %s, only dict or '\n                        'str list is supported.' % str(type(v)))\n    return results\n\n\n# Multi-thread implementation of PSClientComm\nclass MultiThreadPSClientComm(object):\n    def __init__(self, ps):\n        self._ps = ps\n\n    def push(self, base_step, sub_step, grads):\n        if len(grads) > 0:\n            self._ps.push(base_step, sub_step, grads)\n\n    def pull(self, names=None, min_step=0):\n        return self._ps.pull(names=names, min_step=min_step)\n\n\n# ParameterSererClient uses PSClientComm for ps data transfer.\nclass ParameterServerClient(object):\n    def __init__(self,\n                 ps_configs=None,\n                 comm_class=MultiThreadPSClientComm,\n                 partition_func=no_partition):\n        self._ps_size = 1 if partition_func == no_partition else len(ps_configs)\n        self._partition_func = partition_func\n        self._clients = [comm_class(ps_configs[i])\n                         for i in range(self._ps_size)]\n        self._base_step = [0 for _ in range(self._ps_size)]\n\n    def push(self, sub_step=0, grads=None):\n        partition_result = self._partition_func(grads, self._ps_size)\n        # TODO: multithread optimization, one thread per ps communication.\n        for index, g in enumerate(partition_result):\n            self._clients[index].push(self._base_step[index],\n                                      sub_step, g)\n\n    def pull(self, min_step=0, names=None):\n        pull_result = {}\n        partition_result = self._partition_func(names, self._ps_size)\n        # TODO: multithread optimization, one thread per ps communication.\n        for index, n in enumerate(partition_result):\n            ps_step, ps_vars = self._clients[index].pull(\n                names=n, min_step=min_step)\n            self._base_step[index] = ps_step\n            pull_result.update(ps_vars)\n        return self.get_min_base_step(), pull_result\n\n    def get_min_base_step(self):\n        return min(self._base_step)\n", "path": "python/tflib/ps/ps_client.py"}], "after_files": [{"content": "from binascii import crc32\n\n# No partition, all in the first ps\ndef no_partition(v, ps_size):\n    return [v]\n\n\n# Partition v according to the name hashing.\ndef hash_partition(v, ps_size):\n    # a simple stable string hash\n    def _hash(s):\n        return crc32(s.encode())\n\n    if v is None:\n        return [None for i in range(ps_size)]\n    if len(v) == 0:\n        raise ValueError('Empty input used in HashPartition.')\n    if ps_size == 1:\n        return [v]\n    if isinstance(v, dict):\n        # (name, data) dict from push\n        results = [{} for i in range(ps_size)]\n        for name, data in v.items():\n            index = _hash(name) % ps_size\n            results[index][name] = data\n    elif isinstance(v, list):\n        # name list from pull\n        results = [[] for i in range(ps_size)]\n        for name in v:\n            index = _hash(name) % ps_size\n            results[index].append(name)\n    else:\n        raise TypeError('Illegal v type %s, only dict or '\n                        'str list is supported.' % str(type(v)))\n    return results\n\n\n# Multi-thread implementation of PSClientComm\nclass MultiThreadPSClientComm(object):\n    def __init__(self, ps):\n        self._ps = ps\n\n    def push(self, base_step, sub_step, grads):\n        if len(grads) > 0:\n            self._ps.push(base_step, sub_step, grads)\n\n    def pull(self, names=None, min_step=0):\n        return self._ps.pull(names=names, min_step=min_step)\n\n\n# ParameterSererClient uses PSClientComm for ps data transfer.\nclass ParameterServerClient(object):\n    def __init__(self,\n                 ps_configs=None,\n                 comm_class=MultiThreadPSClientComm,\n                 partition_func=no_partition):\n        self._ps_size = 1 if partition_func == no_partition else len(ps_configs)\n        self._partition_func = partition_func\n        self._clients = [comm_class(ps_configs[i])\n                         for i in range(self._ps_size)]\n        self._base_step = [0 for _ in range(self._ps_size)]\n\n    def push(self, sub_step=0, grads=None):\n        partition_result = self._partition_func(grads, self._ps_size)\n        # TODO: multithread optimization, one thread per ps communication.\n        for index, g in enumerate(partition_result):\n            self._clients[index].push(self._base_step[index],\n                                      sub_step, g)\n\n    def pull(self, min_step=0, names=None):\n        pull_result = {}\n        partition_result = self._partition_func(names, self._ps_size)\n        # TODO: multithread optimization, one thread per ps communication.\n        for index, n in enumerate(partition_result):\n            ps_step, ps_vars = self._clients[index].pull(\n                names=n, min_step=min_step)\n            self._base_step[index] = ps_step\n            pull_result.update(ps_vars)\n        return self.get_min_base_step(), pull_result\n\n    def get_min_base_step(self):\n        return min(self._base_step)\n", "path": "python/tflib/ps/ps_client.py"}]}