Skip to content

Commit 303487d

Browse files
committed
multithread 2D approx
1 parent 34e1018 commit 303487d

2 files changed

Lines changed: 88 additions & 35 deletions

File tree

matexp/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def _measure_speed(f, num_states, inputs, conserve_sum, target):
126126
for inp in inputs:
127127
input_arrays.append(inp.random(num_instances, np.float64, xp))
128128
input_arrays.append(input_indicies)
129-
_clear_CPU_cache(xp)
129+
_clear_data_cache(xp)
130130
time.sleep(0) # Try to avoid task switching while running.
131131
os.sched_yield()
132132
if target == 'cuda':
@@ -141,7 +141,7 @@ def _measure_speed(f, num_states, inputs, conserve_sum, target):
141141
elapsed_times[trial] = time.thread_time_ns() - start_time
142142
return np.min(elapsed_times) / num_instances
143143

144-
def _clear_CPU_cache(array_module):
144+
def _clear_data_cache(array_module):
145145
# Read and then write back 32MB of data. Assuming that the CPU is using a
146146
# least-recently-used replacement policy, touching every piece of data once
147147
# should be sufficient to put it into the cache.

matexp/approx.py

Lines changed: 86 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,18 @@ def _alloc_table(self):
161161
self.table = np.ndarray(table_shape, dtype=np.float64, buffer=self.table_sm.buf)
162162
_table_name_autoinc += 1
163163

164+
def _make_table(self):
165+
from . import _thread_pool
166+
args = zip(repeat(self.table_name),
167+
*(repeat(inp) for inp in self.model.inputs),
168+
repeat(self.model.num_states),
169+
repeat(self.polynomial),
170+
repeat(len(self.samples)),
171+
iter(self.samples))
172+
rss_sum = sum(map(self._table_kernel, args)) # Single threaded
173+
# rss_sum = sum(_thread_pool.map(self._table_kernel, list(args), chunksize=1)) # Multithreaded
174+
self.rmse = (rss_sum / self.num_states**2 / len(self.samples)) ** .5
175+
164176
def __del__(self):
165177
if self.table_sm is not None:
166178
self.table_sm.close()
@@ -250,22 +262,11 @@ def _polynomial_basis(input1_locations, num_terms):
250262
A[:, power] = input1_locations ** power
251263
return A
252264

253-
def _make_table(self):
254-
from . import _thread_pool
255-
args = zip(repeat(self.table_name),
256-
repeat(self.model.input1),
257-
repeat(self.model.num_states),
258-
repeat(self.num_terms),
259-
repeat(len(self.samples)),
260-
iter(self.samples))
261-
# rss_sum = sum(map(self._table_kernel, args)) # Single threaded
262-
rss_sum = sum(_thread_pool.map(self._table_kernel, list(args), chunksize=1)) # Multithreaded
263-
self.rmse = (rss_sum / self.num_states**2 / len(self.samples)) ** .5
264-
265265
@staticmethod
266266
def _table_kernel(args):
267267
# Unpack the arguments.
268-
table_name, input1, num_states, num_terms, num_samples, ((bucket_index,), data_range) = args
268+
table_name, input1, num_states, polynomial, num_samples, ((bucket_index,), data_range) = args
269+
num_terms = polynomial.num_terms
269270
(inputs_sm,), samples_sm = MatrixSamples._get_sm_weakref(1)
270271
table_sm = SharedMemory(table_name, False)
271272
inputs_shape = (num_samples,)
@@ -335,26 +336,45 @@ def __init__(self, samples, polynomial):
335336
self._alloc_table()
336337
self._make_table()
337338

338-
def _make_table(self):
339-
self.table = np.empty([self.input1.num_buckets, self.input2.num_buckets,
340-
self.num_states, self.num_states, self.num_terms])
341-
def compute_chunk(bucket_data):
342-
(bucket_index1, bucket_index2), (input1_values, input2_values), exact_data = bucket_data
343-
# Scale the inputs into the range [0,1].
344-
input1_locations = self.input1.get_bucket_value(input1_values) - bucket_index1
345-
input2_locations = self.input2.get_bucket_value(input2_values) - bucket_index2
346-
# Make an approximation for each entry in the matrix.
347-
A = np.empty([len(input1_values), self.num_terms])
348-
for term, (power1, power2) in enumerate(self.polynomial.terms):
349-
A[:, term] = (input1_locations ** power1) * (input2_locations ** power2)
350-
B = exact_data.reshape(-1, self.num_states**2)
351-
coef, rss = np.linalg.lstsq(A, B, rcond=None)[:2]
352-
coef = coef.reshape(self.num_terms, self.num_states, self.num_states).transpose(1,2,0)
353-
self.table[bucket_index1, bucket_index2, :, :, :] = coef
354-
return np.sum(rss)
355-
from . import _thread_pool
356-
rss_sum = sum(_thread_pool.map(compute_chunk, self.samples, chunksize=1))
357-
self.rmse = (rss_sum / self.num_states**2 / len(self.samples)) ** .5
339+
@staticmethod
340+
def _polynomial_basis(input1_locations, input2_locations, polynomial):
341+
# Make an approximation for each entry in the matrix.
342+
A = np.empty([len(input1_locations), polynomial.num_terms])
343+
for term, (power1, power2) in enumerate(polynomial.terms):
344+
A[:, term] = (input1_locations ** power1) * (input2_locations ** power2)
345+
return A
346+
347+
@staticmethod
348+
def _table_kernel(args):
349+
# Unpack the arguments.
350+
(table_name, input1, input2, num_states, polynomial,
351+
num_samples, ((bucket_index1, bucket_index2,), data_range)) = args
352+
num_terms = polynomial.num_terms
353+
# Setup the shared memory.
354+
(input1_sm, input2_sm), samples_sm = MatrixSamples._get_sm_weakref(2)
355+
table_sm = SharedMemory(table_name, False)
356+
inputs_shape = (num_samples,)
357+
samples_shape = (num_samples, num_states, num_states)
358+
table_shape = (input1.num_buckets, input2.num_buckets, num_states, num_states, num_terms)
359+
input1_buf = np.ndarray(inputs_shape, dtype=np.float64, buffer=input1_sm.buf)
360+
input2_buf = np.ndarray(inputs_shape, dtype=np.float64, buffer=input2_sm.buf)
361+
samples_buf = np.ndarray(samples_shape, dtype=np.float64, buffer=samples_sm.buf)
362+
table_buf = np.ndarray(table_shape, dtype=np.float64, buffer=table_sm.buf)
363+
# Slice out the current bucket's samples.
364+
num_samples = data_range[1] - data_range[0]
365+
input1_buf = input1_buf[data_range[0] : data_range[1]]
366+
input2_buf = input2_buf[data_range[0] : data_range[1]]
367+
samples_buf = samples_buf[data_range[0] : data_range[1]]
368+
# Scale the inputs into the range [0,1].
369+
input1_locations = input1.get_bucket_value(input1_buf) - bucket_index1
370+
input2_locations = input2.get_bucket_value(input2_buf) - bucket_index2
371+
#
372+
A = Approx2D._polynomial_basis(input1_locations, input2_locations, polynomial)
373+
B = samples_buf.reshape(-1, num_states**2)
374+
coef, rss = np.linalg.lstsq(A, B, rcond=None)[:2]
375+
coef = coef.reshape(num_terms, num_states, num_states).transpose(1,2,0)
376+
table_buf[bucket_index1, bucket_index2, :, :, :] = coef
377+
return np.sum(rss)
358378

359379
def approximate_matrix(self, input1, input2):
360380
assert len(input1.shape) == 1 and input1.shape == input2.shape
@@ -368,3 +388,36 @@ def approximate_matrix(self, input1, input2):
368388
basis = basis.T.reshape(num_samples, 1, 1, self.num_terms)
369389
coef = self.table[bucket1_index, bucket2_index]
370390
return np.sum(coef * basis, axis = -1)
391+
392+
@staticmethod
393+
def _error_kernel(args):
394+
# Unpack the arguments.
395+
(table_name, power, (input1, input2), polynomial, num_states,
396+
num_samples, ((bucket_index1, bucket_index2), data_range)) = args
397+
num_terms = polynomial.num_terms
398+
# Access the shared memory.
399+
(input1_sm, input2_sm), samples_sm = MatrixSamples._get_sm_weakref(2)
400+
table_sm = SharedMemory(table_name, False)
401+
inputs_shape = (num_samples,)
402+
samples_shape = (num_samples, num_states, num_states)
403+
table_shape = (input1.num_buckets, input2.num_buckets, num_states, num_states, num_terms)
404+
input1_buf = np.ndarray(inputs_shape, dtype=np.float64, buffer=input1_sm.buf)
405+
input2_buf = np.ndarray(inputs_shape, dtype=np.float64, buffer=input2_sm.buf)
406+
samples_buf = np.ndarray(samples_shape, dtype=np.float64, buffer=samples_sm.buf)
407+
table_buf = np.ndarray(table_shape, dtype=np.float64, buffer=table_sm.buf)
408+
# Slice out one chunk of data.
409+
num_samples = data_range[1] - data_range[0]
410+
input1_buf = input1_buf[data_range[0] : data_range[1]]
411+
input2_buf = input2_buf[data_range[0] : data_range[1]]
412+
exact = samples_buf[data_range[0] : data_range[1]]
413+
# Evaluate the approximation.
414+
input1_index, input1_location = input1._get_bucket_location_array(input1_buf)
415+
input2_index, input2_location = input2._get_bucket_location_array(input2_buf)
416+
basis = Approx2D._polynomial_basis(input1_location, input2_location, polynomial)
417+
basis = basis.reshape(num_samples, 1, 1, num_terms)
418+
coef = table_buf[bucket_index1, bucket_index2, :, :, :]
419+
approx = np.sum(coef * basis, axis = -1)
420+
# Increase the timestep to 1 ms
421+
approx = np.linalg.matrix_power(approx, power)
422+
exact = np.linalg.matrix_power(exact, power)
423+
return np.max(np.abs(approx - exact))

0 commit comments

Comments
 (0)