vector-quantize-pytorch 1.18.7__tar.gz → 1.19.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/PKG-INFO +36 -2
  2. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/README.md +35 -1
  3. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/pyproject.toml +1 -1
  4. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/tests/test_readme.py +18 -0
  5. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/residual_fsq.py +3 -3
  6. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/residual_lfq.py +5 -5
  7. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/residual_vq.py +44 -16
  8. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/.github/workflows/build.yml +0 -0
  9. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/.github/workflows/python-publish.yml +0 -0
  10. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/.github/workflows/test.yml +0 -0
  11. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/.gitignore +0 -0
  12. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/LICENSE +0 -0
  13. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/examples/autoencoder.py +0 -0
  14. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/examples/autoencoder_fsq.py +0 -0
  15. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/examples/autoencoder_lfq.py +0 -0
  16. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/images/fsq.png +0 -0
  17. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/images/lfq.png +0 -0
  18. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/images/vq.png +0 -0
  19. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/ruff.toml +0 -0
  20. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/tests/test_latent_quantization.py +0 -0
  21. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/__init__.py +0 -0
  22. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/finite_scalar_quantization.py +0 -0
  23. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/latent_quantization.py +0 -0
  24. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/lookup_free_quantization.py +0 -0
  25. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/random_projection_quantizer.py +0 -0
  26. {vector_quantize_pytorch-1.18.7 → vector_quantize_pytorch-1.19.0}/vector_quantize_pytorch/vector_quantize_pytorch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: vector-quantize-pytorch
3
- Version: 1.18.7
3
+ Version: 1.19.0
4
4
  Summary: Vector Quantization - Pytorch
5
5
  Project-URL: Homepage, https://pypi.org/project/vector-quantize-pytorch/
6
6
  Project-URL: Repository, https://github.com/lucidrains/vector-quantizer-pytorch
@@ -144,6 +144,29 @@ quantized, indices, commit_loss = residual_vq(x)
144
144
  # (1, 1024, 256), (2, 1, 1024, 8), (2, 1, 8)
145
145
  ```
146
146
 
147
+
148
+ <a href="https://arxiv.org/abs/2305.05065">This paper</a> out of Google Deepmind claims that residual vector quantization can induce hierarchical semantic ids for building a recommender system. In their scheme, they use increasing number of codes across depth for it to work. This repository supports that scheme as so
149
+
150
+ ```python
151
+ import torch
152
+ from vector_quantize_pytorch import ResidualVQ
153
+
154
+ residual_vq = ResidualVQ(
155
+ dim = 2,
156
+ codebook_size = (5, 128, 256), # from top most hierarchy to lowest, 5 codes, 128 codes, then 256 codes
157
+ )
158
+
159
+ x = torch.randn(2, 2, 2)
160
+
161
+ residual_vq.train()
162
+
163
+ quantized, indices, commit_loss = residual_vq(x, freeze_codebook = True)
164
+
165
+ quantized_out = residual_vq.get_output_from_indices(indices)
166
+
167
+ assert torch.allclose(quantized, quantized_out, atol = 1e-5)
168
+ ```
169
+
147
170
  ## Initialization
148
171
 
149
172
  The SoundStream paper proposes that the codebook should be initialized by the kmeans centroids of the first batch. You can easily turn on this feature with one flag `kmeans_init = True`, for either `VectorQuantize` or `ResidualVQ` class
@@ -757,4 +780,15 @@ assert loss.item() >= 0
757
780
  volume = {abs/2410.06424},
758
781
  url = {https://api.semanticscholar.org/CorpusID:273229218}
759
782
  }
760
- ```
783
+ ```
784
+
785
+ ```bibtex
786
+ @article{Rajput2023RecommenderSW,
787
+ title = {Recommender Systems with Generative Retrieval},
788
+ author = {Shashank Rajput and Nikhil Mehta and Anima Singh and Raghunandan H. Keshavan and Trung Hieu Vu and Lukasz Heldt and Lichan Hong and Yi Tay and Vinh Q. Tran and Jonah Samost and Maciej Kula and Ed H. Chi and Maheswaran Sathiamoorthy},
789
+ journal = {ArXiv},
790
+ year = {2023},
791
+ volume = {abs/2305.05065},
792
+ url = {https://api.semanticscholar.org/CorpusID:258564854}
793
+ }
794
+ ```
@@ -100,6 +100,29 @@ quantized, indices, commit_loss = residual_vq(x)
100
100
  # (1, 1024, 256), (2, 1, 1024, 8), (2, 1, 8)
101
101
  ```
102
102
 
103
+
104
+ <a href="https://arxiv.org/abs/2305.05065">This paper</a> out of Google Deepmind claims that residual vector quantization can induce hierarchical semantic ids for building a recommender system. In their scheme, they use increasing number of codes across depth for it to work. This repository supports that scheme as so
105
+
106
+ ```python
107
+ import torch
108
+ from vector_quantize_pytorch import ResidualVQ
109
+
110
+ residual_vq = ResidualVQ(
111
+ dim = 2,
112
+ codebook_size = (5, 128, 256), # from top most hierarchy to lowest, 5 codes, 128 codes, then 256 codes
113
+ )
114
+
115
+ x = torch.randn(2, 2, 2)
116
+
117
+ residual_vq.train()
118
+
119
+ quantized, indices, commit_loss = residual_vq(x, freeze_codebook = True)
120
+
121
+ quantized_out = residual_vq.get_output_from_indices(indices)
122
+
123
+ assert torch.allclose(quantized, quantized_out, atol = 1e-5)
124
+ ```
125
+
103
126
  ## Initialization
104
127
 
105
128
  The SoundStream paper proposes that the codebook should be initialized by the kmeans centroids of the first batch. You can easily turn on this feature with one flag `kmeans_init = True`, for either `VectorQuantize` or `ResidualVQ` class
@@ -713,4 +736,15 @@ assert loss.item() >= 0
713
736
  volume = {abs/2410.06424},
714
737
  url = {https://api.semanticscholar.org/CorpusID:273229218}
715
738
  }
716
- ```
739
+ ```
740
+
741
+ ```bibtex
742
+ @article{Rajput2023RecommenderSW,
743
+ title = {Recommender Systems with Generative Retrieval},
744
+ author = {Shashank Rajput and Nikhil Mehta and Anima Singh and Raghunandan H. Keshavan and Trung Hieu Vu and Lukasz Heldt and Lichan Hong and Yi Tay and Vinh Q. Tran and Jonah Samost and Maciej Kula and Ed H. Chi and Maheswaran Sathiamoorthy},
745
+ journal = {ArXiv},
746
+ year = {2023},
747
+ volume = {abs/2305.05065},
748
+ url = {https://api.semanticscholar.org/CorpusID:258564854}
749
+ }
750
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "vector-quantize-pytorch"
3
- version = "1.18.7"
3
+ version = "1.19.0"
4
4
  description = "Vector Quantization - Pytorch"
5
5
  authors = [
6
6
  { name = "Phil Wang", email = "lucidrains@gmail.com" }
@@ -201,6 +201,24 @@ def test_rq():
201
201
  x = torch.randn(1, 1024, 512)
202
202
  indices = quantizer(x)
203
203
 
204
+ def test_tiger():
205
+ from vector_quantize_pytorch import ResidualVQ
206
+
207
+ residual_vq = ResidualVQ(
208
+ dim = 2,
209
+ codebook_size = (5, 128, 256),
210
+ )
211
+
212
+ x = torch.randn(2, 2, 2)
213
+
214
+ residual_vq.train()
215
+
216
+ quantized, indices, commit_loss = residual_vq(x, freeze_codebook = True)
217
+
218
+ quantized_out = residual_vq.get_output_from_indices(indices) # pass your indices into here, but the indices must come during .eval(), as during training some of the indices are dropped out (-1)
219
+
220
+ assert torch.allclose(quantized, quantized_out, atol = 1e-5)
221
+
204
222
  def test_fsq():
205
223
  from vector_quantize_pytorch import FSQ
206
224
 
@@ -185,7 +185,7 @@ class ResidualFSQ(Module):
185
185
  # check if seed is manually passed in
186
186
 
187
187
  if not exists(rand_quantize_dropout_fixed_seed):
188
- rand_quantize_dropout_fixed_seed = get_maybe_sync_seed()
188
+ rand_quantize_dropout_fixed_seed = get_maybe_sync_seed(device)
189
189
 
190
190
  rand = random.Random(rand_quantize_dropout_fixed_seed)
191
191
 
@@ -296,7 +296,7 @@ class GroupedResidualFSQ(Module):
296
296
  x,
297
297
  return_all_codes = False
298
298
  ):
299
- shape, split_dim = x.shape, self.split_dim
299
+ shape, split_dim, device = x.shape, self.split_dim, x.device
300
300
  assert shape[split_dim] == self.dim
301
301
 
302
302
  # split the feature dimension into groups
@@ -305,7 +305,7 @@ class GroupedResidualFSQ(Module):
305
305
 
306
306
  forward_kwargs = dict(
307
307
  return_all_codes = return_all_codes,
308
- rand_quantize_dropout_fixed_seed = get_maybe_sync_seed()
308
+ rand_quantize_dropout_fixed_seed = get_maybe_sync_seed(device)
309
309
  )
310
310
 
311
311
  # invoke residual vq on each group
@@ -31,8 +31,8 @@ def round_up_multiple(num, mult):
31
31
  def is_distributed():
32
32
  return dist.is_initialized() and dist.get_world_size() > 1
33
33
 
34
- def get_maybe_sync_seed(max_size = 10_000):
35
- rand_int = torch.randint(0, max_size, ())
34
+ def get_maybe_sync_seed(device, max_size = 10_000):
35
+ rand_int = torch.randint(0, max_size, (), device = device)
36
36
 
37
37
  if is_distributed():
38
38
  dist.all_reduce(rand_int)
@@ -162,7 +162,7 @@ class ResidualLFQ(Module):
162
162
  # check if seed is manually passed in
163
163
 
164
164
  if not exists(rand_quantize_dropout_fixed_seed):
165
- rand_quantize_dropout_fixed_seed = get_maybe_sync_seed()
165
+ rand_quantize_dropout_fixed_seed = get_maybe_sync_seed(device)
166
166
 
167
167
  rand = random.Random(rand_quantize_dropout_fixed_seed)
168
168
 
@@ -262,7 +262,7 @@ class GroupedResidualLFQ(Module):
262
262
  mask = None,
263
263
  return_all_codes = False
264
264
  ):
265
- shape, split_dim = x.shape, self.split_dim
265
+ shape, split_dim, device = x.shape, self.split_dim, x.device
266
266
  assert shape[split_dim] == self.dim
267
267
 
268
268
  # split the feature dimension into groups
@@ -272,7 +272,7 @@ class GroupedResidualLFQ(Module):
272
272
  forward_kwargs = dict(
273
273
  mask = mask,
274
274
  return_all_codes = return_all_codes,
275
- rand_quantize_dropout_fixed_seed = get_maybe_sync_seed()
275
+ rand_quantize_dropout_fixed_seed = get_maybe_sync_seed(device)
276
276
  )
277
277
 
278
278
  # invoke residual vq on each group
@@ -1,5 +1,4 @@
1
1
  from __future__ import annotations
2
- from typing import List
3
2
 
4
3
  import random
5
4
  from math import ceil
@@ -28,6 +27,12 @@ def first(it):
28
27
  def default(val, d):
29
28
  return val if exists(val) else d
30
29
 
30
+ def cast_tuple(t, length = 1):
31
+ return t if isinstance(t, tuple) else ((t,) * length)
32
+
33
+ def unique(arr):
34
+ return list({*arr})
35
+
31
36
  def round_up_multiple(num, mult):
32
37
  return ceil(num / mult) * mult
33
38
 
@@ -36,8 +41,8 @@ def round_up_multiple(num, mult):
36
41
  def is_distributed():
37
42
  return dist.is_initialized() and dist.get_world_size() > 1
38
43
 
39
- def get_maybe_sync_seed(max_size = 10_000):
40
- rand_int = torch.randint(0, max_size, ())
44
+ def get_maybe_sync_seed(device, max_size = 10_000):
45
+ rand_int = torch.randint(0, max_size, (), device = device)
41
46
 
42
47
  if is_distributed():
43
48
  dist.all_reduce(rand_int)
@@ -110,7 +115,8 @@ class ResidualVQ(Module):
110
115
  self,
111
116
  *,
112
117
  dim,
113
- num_quantizers,
118
+ num_quantizers: int | None = None,
119
+ codebook_size: int | tuple[int, ...],
114
120
  codebook_dim = None,
115
121
  shared_codebook = False,
116
122
  heads = 1,
@@ -124,6 +130,8 @@ class ResidualVQ(Module):
124
130
  ):
125
131
  super().__init__()
126
132
  assert heads == 1, 'residual vq is not compatible with multi-headed codes'
133
+ assert exists(num_quantizers) or isinstance(codebook_size, tuple)
134
+
127
135
  codebook_dim = default(codebook_dim, dim)
128
136
  codebook_input_dim = codebook_dim * heads
129
137
 
@@ -132,8 +140,6 @@ class ResidualVQ(Module):
132
140
  self.project_out = nn.Linear(codebook_input_dim, dim) if requires_projection else nn.Identity()
133
141
  self.has_projections = requires_projection
134
142
 
135
- self.num_quantizers = num_quantizers
136
-
137
143
  self.accept_image_fmap = accept_image_fmap
138
144
 
139
145
  self.implicit_neural_codebook = implicit_neural_codebook
@@ -150,7 +156,21 @@ class ResidualVQ(Module):
150
156
  manual_in_place_optimizer_update = True
151
157
  )
152
158
 
153
- self.layers = ModuleList([VectorQuantize(dim = codebook_dim, codebook_dim = codebook_dim, accept_image_fmap = accept_image_fmap, **vq_kwargs) for _ in range(num_quantizers)])
159
+ # take care of maybe different codebook sizes across depth, used in TIGER paper https://arxiv.org/abs/2305.05065
160
+
161
+ codebook_sizes = cast_tuple(codebook_size, num_quantizers)
162
+
163
+ num_quantizers = len(codebook_sizes)
164
+ self.num_quantizers = num_quantizers
165
+
166
+ assert len(codebook_sizes) == num_quantizers
167
+
168
+ self.codebook_sizes = codebook_sizes
169
+ self.uniform_codebook_size = len(unique(codebook_sizes)) == 1
170
+
171
+ # define vq across layers
172
+
173
+ self.layers = ModuleList([VectorQuantize(dim = codebook_dim, codebook_size = layer_codebook_size, codebook_dim = codebook_dim, accept_image_fmap = accept_image_fmap, **vq_kwargs) for layer_codebook_size in codebook_sizes])
154
174
 
155
175
  assert all([not vq.has_projections for vq in self.layers])
156
176
 
@@ -167,6 +187,8 @@ class ResidualVQ(Module):
167
187
 
168
188
  if implicit_neural_codebook:
169
189
  self.mlps = ModuleList([MLP(dim = codebook_dim, l2norm_output = first(self.layers).use_cosine_sim, **mlp_kwargs) for _ in range(num_quantizers - 1)])
190
+ else:
191
+ self.mlps = (None,) * (num_quantizers - 1)
170
192
 
171
193
  # sharing codebook logic
172
194
 
@@ -175,6 +197,8 @@ class ResidualVQ(Module):
175
197
  if not shared_codebook:
176
198
  return
177
199
 
200
+ assert self.uniform_codebook_size
201
+
178
202
  first_vq, *rest_vq = self.layers
179
203
  codebook = first_vq._codebook
180
204
 
@@ -192,8 +216,13 @@ class ResidualVQ(Module):
192
216
  @property
193
217
  def codebooks(self):
194
218
  codebooks = [layer._codebook.embed for layer in self.layers]
195
- codebooks = torch.stack(codebooks, dim = 0)
196
- codebooks = rearrange(codebooks, 'q 1 c d -> q c d')
219
+
220
+ codebooks = tuple(rearrange(codebook, '1 ... -> ...') for codebook in codebooks)
221
+
222
+ if not self.uniform_codebook_size:
223
+ return codebooks
224
+
225
+ codebooks = torch.stack(codebooks)
197
226
  return codebooks
198
227
 
199
228
  def get_codes_from_indices(self, indices):
@@ -216,13 +245,12 @@ class ResidualVQ(Module):
216
245
  mask = indices == -1.
217
246
  indices = indices.masked_fill(mask, 0) # have it fetch a dummy code to be masked out later
218
247
 
219
- if not self.implicit_neural_codebook:
220
- # gather all the codes
248
+ if not self.implicit_neural_codebook and self.uniform_codebook_size:
221
249
 
222
250
  all_codes = get_at('q [c] d, b n q -> q b n d', self.codebooks, indices)
223
251
 
224
252
  else:
225
- # else if using implicit neural codebook, codes will need to be derived layer by layer
253
+ # else if using implicit neural codebook, or non uniform codebook sizes, codes will need to be derived layer by layer
226
254
 
227
255
  code_transform_mlps = (None, *self.mlps)
228
256
 
@@ -261,7 +289,7 @@ class ResidualVQ(Module):
261
289
  self,
262
290
  x,
263
291
  mask = None,
264
- indices: Tensor | List[Tensor] | None = None,
292
+ indices: Tensor | list[Tensor] | None = None,
265
293
  return_all_codes = False,
266
294
  sample_codebook_temp = None,
267
295
  freeze_codebook = False,
@@ -296,7 +324,7 @@ class ResidualVQ(Module):
296
324
  # check if seed is manually passed in
297
325
 
298
326
  if not exists(rand_quantize_dropout_fixed_seed):
299
- rand_quantize_dropout_fixed_seed = get_maybe_sync_seed()
327
+ rand_quantize_dropout_fixed_seed = get_maybe_sync_seed(device)
300
328
 
301
329
  rand = random.Random(rand_quantize_dropout_fixed_seed)
302
330
 
@@ -452,7 +480,7 @@ class GroupedResidualVQ(Module):
452
480
  freeze_codebook = False,
453
481
  mask = None,
454
482
  ):
455
- shape, split_dim = x.shape, self.split_dim
483
+ shape, split_dim, device = x.shape, self.split_dim, x.device
456
484
  assert shape[split_dim] == self.dim
457
485
 
458
486
  # split the feature dimension into groups
@@ -468,7 +496,7 @@ class GroupedResidualVQ(Module):
468
496
  sample_codebook_temp = sample_codebook_temp,
469
497
  mask = mask,
470
498
  freeze_codebook = freeze_codebook,
471
- rand_quantize_dropout_fixed_seed = get_maybe_sync_seed()
499
+ rand_quantize_dropout_fixed_seed = get_maybe_sync_seed(device)
472
500
  )
473
501
 
474
502
  # invoke residual vq on each group