You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

cstable.py 7.8 kB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. from hetu.gpu_ops.executor import path_to_lib
  2. from hetu import ndarray
  3. from hetu import get_worker_communicate
  4. import numpy as np
  5. import sys
  6. import os
  7. from functools import partial
  8. """
  9. CacheSparseTable:
  10. length, width: the length and width of the whole embedding table
  11. limit: the max number of embedding lines stored in cache
  12. node_id: the unique node_id in the model
  13. policy: cache policy, LRU or LFU
  14. """
  15. class CacheSparseTable:
  16. def __init__(self, limit, length, width, node_id, policy="LRU", bound=100):
  17. # make sure we open libps.so first
  18. comm = get_worker_communicate()
  19. sys.path.append(os.path.dirname(__file__)+"/../../build/lib")
  20. import hetu_cache
  21. policy = policy.lower()
  22. if policy == "lru":
  23. self.cache = hetu_cache.LRUCache(limit, length, width, node_id)
  24. elif policy == "lfu":
  25. self.cache = hetu_cache.LFUCache(limit, length, width, node_id)
  26. elif policy == "lfuopt":
  27. self.cache = hetu_cache.LFUOptCache(limit, length, width, node_id)
  28. else:
  29. raise NotImplementedError(policy)
  30. self.cache.pull_bound = bound
  31. self.cache.push_bound = bound
  32. comm.BarrierWorker()
  33. """
  34. embedding_lookup:
  35. keys: a list of keys to lookup
  36. dest: target memory space to write to
  37. sync: async call of sync call
  38. if async, a wait_t is returned, use wait.wait() to wait until it finish.
  39. if async, must make sure keys and dest are alive throughout the call
  40. """
  41. def embedding_lookup(self, keys, dest, sync=False):
  42. wait = None
  43. if type(keys) is np.ndarray and type(dest) is np.ndarray:
  44. assert dest.shape == (keys.size, self.width)
  45. assert keys.dtype == np.uint64
  46. assert dest.dtype == np.float32
  47. wait = self.cache.embedding_lookup(keys, dest)
  48. elif type(keys) is ndarray.NDArray and type(dest) is ndarray.NDArray:
  49. assert dest.shape == (*keys.shape, self.width)
  50. assert not ndarray.is_gpu_ctx(keys.ctx)
  51. assert not ndarray.is_gpu_ctx(dest.ctx)
  52. wait = self.cache.embedding_lookup_raw(
  53. keys.handle.contents.data, dest.handle.contents.data, np.prod(keys.shape))
  54. else:
  55. raise TypeError
  56. if sync:
  57. wait.wait()
  58. else:
  59. return wait
  60. """
  61. embedding_lookup:
  62. keys: a list of keys to update
  63. grads: gradients to send
  64. sync: async call of sync call
  65. if async, a wait_t is returned, use wait.wait() to wait until it finish.
  66. if async, must make sure keys and dest are alive throughout the call
  67. """
  68. def embedding_update(self, keys, grads, sync=False):
  69. wait = None
  70. if type(keys) is np.ndarray and type(grads) is np.ndarray:
  71. assert grads.shape == (keys.size, self.width)
  72. assert keys.dtype == np.uint64
  73. assert grads.dtype == np.float32
  74. wait = self.cache.embedding_update(keys, grads)
  75. elif type(keys) is ndarray.NDArray and type(grads) is ndarray.NDArray:
  76. assert grads.shape == (*keys.shape, self.width)
  77. assert not ndarray.is_gpu_ctx(keys.ctx)
  78. assert not ndarray.is_gpu_ctx(grads.ctx)
  79. wait = self.cache.embedding_update_raw(
  80. keys.handle.contents.data, grads.handle.contents.data, np.prod(keys.shape))
  81. else:
  82. raise TypeError
  83. if sync:
  84. wait.wait()
  85. else:
  86. return wait
  87. def embedding_push_pull(self, pullkeys, dest, pushkeys, grads, sync=False):
  88. wait = None
  89. if type(pullkeys) is ndarray.NDArray and type(dest) is ndarray.NDArray and \
  90. type(pushkeys) is ndarray.NDArray and type(grads) is ndarray.NDArray:
  91. assert grads.shape == (*pushkeys.shape, self.width)
  92. assert dest.shape == (*pullkeys.shape, self.width)
  93. assert not ndarray.is_gpu_ctx(pullkeys.ctx)
  94. assert not ndarray.is_gpu_ctx(pushkeys.ctx)
  95. assert not ndarray.is_gpu_ctx(grads.ctx)
  96. assert not ndarray.is_gpu_ctx(dest.ctx)
  97. wait = self.cache.embedding_push_pull_raw(
  98. pullkeys.handle.contents.data, dest.handle.contents.data, np.prod(
  99. pullkeys.shape),
  100. pushkeys.handle.contents.data, grads.handle.contents.data, np.prod(
  101. pushkeys.shape)
  102. )
  103. else:
  104. raise TypeError
  105. if sync:
  106. wait.wait()
  107. else:
  108. return wait
  109. @property
  110. def width(self):
  111. return self.cache.width
  112. @property
  113. def limit(self):
  114. return self.cache.limit
  115. def perf_enabled(self, enable=True):
  116. self.cache.perf_enabled = enable
  117. @property
  118. def perf(self):
  119. # perf data example [item1, item2...]
  120. # item = "type": pull_or_push, "is_full": is_cache_full, "num_all", num_of_key
  121. # "num_unique": num_of_unique_key, "num_miss": num_of_missed_unique_key,
  122. # "num_evict": num_push_of_eviction, "num_transfered"(if push): miss+outofpushbound+evict
  123. # "num_transfered"(if pull): miss+outofpullbound, "time": last_time_in_ms
  124. return self.cache.perf
  125. # if bypass, directly pull and push the server
  126. def bypass(self):
  127. self.cache.bypass()
  128. def undobypass(self):
  129. self.cache.undo_bypass()
  130. def __repr__(self):
  131. return self.cache.__repr__()
  132. # the following calls are single key call
  133. # for debugging
  134. def lookup(self, key):
  135. return self.cache.lookup(key)
  136. def count(self, key):
  137. return self.cache.count(key)
  138. def insert(self, key, embedding):
  139. return self.cache.insert(key, embedding)
  140. def keys(self):
  141. return self.cache.keys()
  142. # PerfHelperFunction
  143. # miss rate for pull
  144. def overall_miss_rate(self, include_cold_start=False):
  145. if not include_cold_start:
  146. perf = list(filter(lambda x: x["is_full"], self.perf))
  147. else:
  148. perf = self.perf
  149. if not perf:
  150. return -1
  151. pull_perf = list(filter(lambda x: x["type"] == "Pull", perf))
  152. num_all = [x["num_unique"] for x in pull_perf]
  153. num_miss = [x["num_miss"] for x in pull_perf]
  154. return np.sum(num_miss) / np.sum(num_all)
  155. # data rate compared with vanilla sparse pull (ignore cost for idx&version)
  156. def overall_data_rate(self, include_cold_start=False):
  157. if not include_cold_start:
  158. perf = list(filter(lambda x: x["is_full"], self.perf))
  159. else:
  160. perf = self.perf
  161. if not perf:
  162. return -1
  163. num_all = [x["num_all"] for x in perf]
  164. num_miss = [x["num_transfered"] for x in perf]
  165. return np.sum(num_miss) / np.sum(num_all)
  166. def debug_keys(self):
  167. comm = get_worker_communicate()
  168. nrank = comm.nrank()
  169. form = "w" if comm.rank() == 0 else "a"
  170. for i in range(nrank):
  171. if i == comm.rank():
  172. with open("_keys.log".format(comm.rank()), form) as f:
  173. print(*self.keys(), file=f, flush=True)
  174. comm.BarrierWorker()
  175. if comm.rank() != 0:
  176. return
  177. keys = []
  178. with open("_keys.log".format(comm.rank()), "r") as f:
  179. for i in range(nrank):
  180. keys.append(set(map(int, f.readline().split())))
  181. rt = np.zeros([nrank, nrank])
  182. for i in range(nrank):
  183. for j in range(nrank):
  184. if not keys[i]:
  185. continue
  186. rt[i][j] = len(keys[i].intersection(keys[j])) / len(keys[i])
  187. return rt