Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Random Rotation Matrix and Query Scaling Matrix with 1-bit Binary Quantization #2524

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

Vikasht34
Copy link
Collaborator

Description

This change enhances the One-Bit Scalar Quantization process in by refining the Below and Above Threshold Means computation and introducing Random Gaussian Rotation. These improvements optimize both quantization accuracy during indexing and query scaling during retrieval, ensuring better recall and efficiency in k-NN search.

Below and Above Threshold Means for Query Scaling
These means help in query-time scaling, enabling better similarity approximation.

  • During training, each vector dimension is analyzed, and a threshold (mean value) is computed.
  • Each dimension’s values are classified as:
    • Below threshold: Values lower than the computed mean.
    • Above threshold: Values higher than the computed mean.

Random Gaussian Rotation for Robustness
Applies a Gaussian orthonormal rotation matrix to input vectors before quantization.
Used only when the L2/L1 ratio exceeds 0.6, ensuring that rotation is applied selectively for improved data distribution.

Related Issues

Resolves #[Issue number to be closed when this PR is merged]

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff.
  • Public documentation issue/PR created.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

…above and below thresholds for 1 bit Binary Quantization

Signed-off-by: Vikasht34 <[email protected]>
Signed-off-by: Vikasht34 <[email protected]>
@Vikasht34 Vikasht34 force-pushed the main branch 3 times, most recently from f5e801f to dad6872 Compare February 13, 2025 22:42
Signed-off-by: Vikasht34 <[email protected]>
* Represents the mean of all values below the threshold for each dimension.
*/
@Builder.Default
private float[] belowThresholdMeans = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add examples for these, as was done for meanThresholds?

@Builder.Default
private float[] aboveThresholdMeans = null;
@Builder.Default
private double averageL2L1Ratio = 0.0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment on this?

@@ -51,6 +76,20 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(Version.CURRENT.id); // Write the version
quantizationParams.writeTo(out);
out.writeFloatArray(meanThresholds);
out.writeOptionalArray(belowThresholdMeans != null ? new FloatArrayWrapper[] { new FloatArrayWrapper(belowThresholdMeans) } : null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For BWC purposes, writeOptionalArray wont work in a mixed cluster state. We need to check the stream version.

@Builder.Default
private double averageL2L1Ratio = 0.0;
/**
* Rotation matrix used when L2/L1 ratio > 0.6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add more detail? For instance, what are the properties of this matrix. Also, what will happen if L2/L1 ratio is < 0.6

@@ -63,6 +102,23 @@ public OneBitScalarQuantizationState(StreamInput in) throws IOException {
int version = in.readVInt(); // Read the version
this.quantizationParams = new ScalarQuantizationParams(in, version);
this.meanThresholds = in.readFloatArray();
if (Version.fromId(version).onOrAfter(Version.V_3_0_0)) {
// Deserialize belowThresholdMeans using readOptionalArray
FloatArrayWrapper[] wrappedBelowThresholdMeans = in.readOptionalArray(FloatArrayWrapper::new, FloatArrayWrapper[]::new);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why optional on this? Is there a case where its null?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes ..for Version less than 3.0.0

this.array = in.readFloatArray();
}

public float[] getArray() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: lombok getter

float[] belowThresholdMeans = belowAboveMeans.getA();
float[] aboveThresholdMeans = belowAboveMeans.getB();

// Apply the same rotation to below and above threshold means if rotation was applied
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line 60, we apply the rotation on the meanThresholds. Then we calculate the belowAboveMeans with this rotated meanThresholds in line 65. So, shouldnt the returned thresholds already be rotated?

Instead, can we apply rotations to all thresholds at the end (including menathresholds)?

* @throws IOException If an I/O error occurs while retrieving vector data.
* @throws IllegalArgumentException If any vector at the sampled indices is null.
*/
private static Pair<float[], Double> calculateMeanAndL2L1Ratio(TrainingRequest<float[]> trainingRequest, int[] sampledIndices)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a pair, can we make this into some kind of object so that we can continue to extend with additional data?

}
}

// Step 2: Orthogonalize the matrix using the Gram-Schmidt process
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any libraries we can delegate this computation to?

@@ -63,6 +102,23 @@ public OneBitScalarQuantizationState(StreamInput in) throws IOException {
int version = in.readVInt(); // Read the version
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to be bwc? We need to check if the segment was created in a previous version as opposed to the node it is coming from.

@jmazanec15
Copy link
Member

@Vikasht34 For this, I think we probably can break up the random rotation and the computation of mean centers into 2 separate features. I think that they can be added independently.

@Vikasht34
Copy link
Collaborator Author

@Vikasht34 For this, I think we probably can break up the random rotation and the computation of mean centers into 2 separate features. I think that they can be added independently.

IMO ..it should be part of whole ADC ...We have seen random rotation giving advantage of some data sets , It's beeter we get combo of ADC + Random Rotation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants