Skip to content

Commit

Permalink
Add methods for converting MF models to/from gpu (#521)
Browse files Browse the repository at this point in the history
Adds some simple methods to convert BPR and ALS models from a CPU version
to a GPU version. This is useful for when you want to train on the CPU
and evaluate on the GPU (or vice versa).
  • Loading branch information
benfred authored Jan 18, 2022
1 parent cc324b2 commit 4301c8b
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 0 deletions.
14 changes: 14 additions & 0 deletions implicit/cpu/als.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,20 @@ def XtX(self):
self._XtX = X.T.dot(X)
return self._XtX

def to_gpu(self):
"""Converts this model to an equivalent version running on the gpu"""
import implicit.gpu.als

ret = implicit.gpu.als.AlternatingLeastSquares(
factors=self.factors,
regularization=self.regularization,
iterations=self.iterations,
calculate_training_loss=self.calculate_training_loss,
)
ret.user_factors = implicit.gpu.Matrix(self.user_factors)
ret.item_factors = implicit.gpu.Matrix(self.item_factors)
return ret


def least_squares(Cui, X, Y, regularization, num_threads=0):
"""For each user in Cui, calculate factors Xu for them
Expand Down
15 changes: 15 additions & 0 deletions implicit/cpu/bpr.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ class BayesianPersonalizedRanking(MatrixFactorizationBase):

self._check_fit_errors()

def to_gpu(self):
"""Converts this model to an equivalent version running on the gpu"""
import implicit.gpu.bpr

ret = implicit.gpu.bpr.BayesianPersonalizedRanking(
factors=self.factors,
learning_rate=self.learning_rate,
regularization=self.regularization,
iterations=self.iterations,
verify_negative_samples=self.verify_negative_samples,
)
ret.user_factors = implicit.gpu.Matrix(self.user_factors)
ret.item_factors = implicit.gpu.Matrix(self.item_factors)
return ret


@cython.cdivision(True)
@cython.boundscheck(False)
Expand Down
12 changes: 12 additions & 0 deletions implicit/gpu/als.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,15 @@ def XtX(self):
self._XtX = implicit.gpu.Matrix(self.factors, self.factors)
self.solver.calculate_yty(self.user_factors, self._XtX, self.regularization)
return self._XtX

def to_cpu(self):
"""Converts this model to an equivalent version running on the CPU"""
ret = implicit.cpu.als.AlternatingLeastSquares(
factors=self.factors,
regularization=self.regularization,
iterations=self.iterations,
calculate_training_loss=self.calculate_training_loss,
)
ret.user_factors = self.user_factors.to_numpy()
ret.item_factors = self.item_factors.to_numpy()
return ret
13 changes: 13 additions & 0 deletions implicit/gpu/bpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,16 @@ def fit(self, user_items, show_progress=True):
"skipped": f"{100.0 * skipped / total:0.2f}%",
}
)

def to_cpu(self):
"""Converts this model to an equivalent version running on the cpu"""
ret = implicit.cpu.bpr.BayesianPersonalizedRanking(
factors=self.factors,
learning_rate=self.learning_rate,
regularization=self.regularization,
iterations=self.iterations,
verify_negative_samples=self.verify_negative_samples,
)
ret.user_factors = self.user_factors.to_numpy()
ret.item_factors = self.item_factors.to_numpy()
return ret
18 changes: 18 additions & 0 deletions tests/gpu_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import implicit

from .recommender_base_test import get_checker_board


@pytest.mark.skipif(not implicit.gpu.HAS_CUDA, reason="needs cuda build")
@pytest.mark.parametrize("k", [4, 16, 64, 128, 1000])
Expand Down Expand Up @@ -61,3 +63,19 @@ def test_calculate_norms():
)
np_norms = np.linalg.norm(items, axis=1)
assert_allclose(norms, np_norms)


@pytest.mark.skipif(not implicit.gpu.HAS_CUDA, reason="needs cuda build")
@pytest.mark.parametrize(
"model_class", [implicit.als.AlternatingLeastSquares, implicit.bpr.BayesianPersonalizedRanking]
)
@pytest.mark.parametrize("from_gpu", [True, False])
def test_cpu_gpu_conversion(model_class, from_gpu):
print("model_class!", model_class, from_gpu)
model = model_class(use_gpu=from_gpu, factors=32)
user_plays = get_checker_board(50)
model.fit(user_plays)
converted = model.to_cpu() if from_gpu else model.to_gpu()
assert_allclose(
model.recommend(0, user_plays), converted.recommend(0, user_plays), rtol=1e-3, atol=1e-3
)

0 comments on commit 4301c8b

Please sign in to comment.