Skip to content

Add doc string to explain matrix-vector product's SSE code and a test #3124

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

Merged
merged 7 commits into from
Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/Microsoft.ML.CpuMath/CpuMathUtils.netstandard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ internal static partial class CpuMathUtils
public static int GetVectorAlignment()
=> Vector128Alignment;

/// <summary>
/// Check if <paramref name="a"/>'s alignment is suitable to SSE instructions. Returns <see langword="true"/>
/// if <paramref name="a"/>'s alignment is ok and <see langword="false"/> otherwise.
/// </summary>
/// <param name="a">The vector being checked.</param>
/// <returns>Whether <paramref name="a"/> is aligned well.</returns>
private static bool Compat(AlignedArray a)
{
Contracts.AssertValue(a);
Contracts.Assert(a.Size > 0);
return a.CbAlign == Vector128Alignment;
return a.CbAlign % Vector128Alignment == 0;
}

private static unsafe float* Ptr(AlignedArray a, float* p)
Expand All @@ -34,6 +40,19 @@ private static bool Compat(AlignedArray a)
return q;
}

/// <summary>
/// Compute the product of matrix <paramref name="mat"/> (the matrix is flattened because its type is <see cref="AlignedArray"/> instead of a matrix)
/// and a vector <paramref name="src"/>.
Copy link
Contributor

Choose a reason for hiding this comment

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

    /// Compute the product of a flattened matrix <paramref name="mat"/> (flattened because its type is <see cref="AlignedArray"/>) 
    /// and a vector <paramref name="src"/>.

Maybe like this it is a bit easier to read.

Copy link
Member Author

@wschin wschin Apr 10, 2019

Choose a reason for hiding this comment

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

My bad. Thanks.

/// </summary>
/// <param name="tran">Whether to transpose <paramref name="mat"/> before doing any computation.</param>
/// <param name="mat">If <paramref name="tran"/> is <see langword="false"/>, <paramref name="mat"/> is a m-by-n matrix, and the value at the i-th row and the j-th column is indexed by i * n + j in <paramref name="mat"/>.
/// If <paramref name="tran"/> is <see langword="true"/>, <paramref name="mat"/> would be viewed a n-by-m matrix, and the value at the i-th row and the j-th column in the transposed matrix is indexed by j * m + i in the
/// original <paramref name="mat"/>.</param>
/// <param name="src">A n-by-1 matrix, which is also a vector.</param>
/// <param name="dst">A m-by-1 matrix, which is also a vector.</param>
/// <param name="crun">The truncation level of <paramref name="dst"/>. For example, if <paramref name="crun"/> is 2, <paramref name="dst"/>
/// will be considered as a 2-by-1 matrix and therefore elements after its 2nd element will be ignored. If no truncation should happen,
/// set <paramref name="crun"/> to the length of <paramref name="dst"/>.</param>
public static void MatrixTimesSource(bool tran, AlignedArray mat, AlignedArray src, AlignedArray dst, int crun)
{
Contracts.Assert(Compat(mat));
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.ML.CpuMath/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
[assembly: InternalsVisibleTo(assemblyName: "LibSvmWrapper" + InternalPublicKey.Value)]
[assembly: InternalsVisibleTo(assemblyName: "Microsoft.ML.Runtime.NeuralNetworks" + InternalPublicKey.Value)]
[assembly: InternalsVisibleTo(assemblyName: "Microsoft.ML.RServerScoring.NeuralNetworks" + InternalPublicKey.Value)]
[assembly: InternalsVisibleTo(assemblyName: "Microsoft.ML.Tests" + PublicKey.TestValue)]
[assembly: InternalsVisibleTo(assemblyName: "RunTests" + InternalPublicKey.Value)]
[assembly: InternalsVisibleTo(assemblyName: "SseTests" + InternalPublicKey.Value)]
1 change: 1 addition & 0 deletions test/Microsoft.ML.Tests/Microsoft.ML.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ProjectReference Include="..\..\src\Microsoft.ML.TensorFlow.StaticPipe\Microsoft.ML.TensorFlow.StaticPipe.csproj" />
<ProjectReference Include="..\..\src\Microsoft.ML.TensorFlow\Microsoft.ML.TensorFlow.csproj" />
<ProjectReference Include="..\..\src\Microsoft.ML.TimeSeries\Microsoft.ML.TimeSeries.csproj" />
<ProjectReference Include="..\..\src\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.csproj" />
<ProjectReference Include="..\Microsoft.ML.Predictor.Tests\Microsoft.ML.Predictor.Tests.csproj" />
<ProjectReference Include="..\Microsoft.ML.TestFramework\Microsoft.ML.TestFramework.csproj" />
</ItemGroup>
Expand Down
118 changes: 116 additions & 2 deletions test/Microsoft.ML.Tests/TrainerEstimators/MatrixFactorizationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.ML.Data;
using Microsoft.ML.Internal.CpuMath;
using Microsoft.ML.RunTests;
using Microsoft.ML.TestFramework.Attributes;
using Microsoft.ML.Trainers;
Expand Down Expand Up @@ -253,6 +254,29 @@ public void MatrixFactorizationInMemoryData()
Assert.True(pred.Score != 0);
}

internal class MatrixElementZeroBased256By256
{
// Matrix column index starts from 0 and is at most _synthesizedMatrixColumnCount.
[KeyType(_matrixColumnCount)]
public uint MatrixColumnIndex;
// Matrix row index starts from 0 and is at most _synthesizedMatrixRowCount.
[KeyType(_matrixRowCount)]
public uint MatrixRowIndex;
// The value at the MatrixColumnIndex-th column and the MatrixRowIndex-th row in the considered matrix.
public float Value;
}

internal class MatrixElementZeroBasedForScore256By256
{
// Matrix column index starts from 0 and is at most _synthesizedMatrixColumnCount.
[KeyType(_matrixColumnCount)]
public uint MatrixColumnIndex;
// Matrix row index starts from 0 and is at most _synthesizedMatrixRowCount.
[KeyType(_matrixRowCount)]
public uint MatrixRowIndex;
public float Score;
}

internal class MatrixElementZeroBased
{
// Matrix column index starts from 0 and is at most _synthesizedMatrixColumnCount.
Expand All @@ -268,11 +292,9 @@ internal class MatrixElementZeroBased
internal class MatrixElementZeroBasedForScore
{
// Matrix column index starts from 0 and is at most _synthesizedMatrixColumnCount.
// Contieuous=true means that all values from 0 to _synthesizedMatrixColumnCount are allowed keys.
[KeyType(_synthesizedMatrixColumnCount)]
public uint MatrixColumnIndex;
// Matrix row index starts from 0 and is at most _synthesizedMatrixRowCount.
// Contieuous=true means that all values from 0 to _synthesizedMatrixRowCount are allowed keys.
[KeyType(_synthesizedMatrixRowCount)]
public uint MatrixRowIndex;
public float Score;
Expand Down Expand Up @@ -603,5 +625,97 @@ public void OneClassMatrixFactorizationWithUnseenColumnAndRow()
CompareNumbersWithTolerance(0.05511549, testResults[1].Score, digitsOfPrecision: 5);
CompareNumbersWithTolerance(0.00316973357, testResults[2].Score, digitsOfPrecision: 5);
}

const int _matrixColumnCount = 256;
const int _matrixRowCount = 256;

[MatrixFactorizationFact]
public void InspectMatrixFactorizationModel()
{
// Create an in-memory matrix as a list of tuples (column index, row index, value).
// Iterators i and j are column and row indexes, respectively.
var dataMatrix = new List<MatrixElementZeroBased256By256>();
for (uint i = 0; i < _matrixColumnCount; ++i)
for (uint j = 0; j < _matrixRowCount; ++j)
dataMatrix.Add(new MatrixElementZeroBased256By256() { MatrixColumnIndex = i, MatrixRowIndex = j, Value = (i + j) % 5 });

// Convert the in-memory matrix into an IDataView so that ML.NET components can consume it.
var dataView = ML.Data.LoadFromEnumerable(dataMatrix);

// Create a matrix factorization trainer which may consume "Value" as the training label, "MatrixColumnIndex" as the
// matrix's column index, and "MatrixRowIndex" as the matrix's row index.
var mlContext = new MLContext(seed: 1);

var options = new MatrixFactorizationTrainer.Options
{
MatrixColumnIndexColumnName = nameof(MatrixElement.MatrixColumnIndex),
MatrixRowIndexColumnName = nameof(MatrixElement.MatrixRowIndex),
LabelColumnName = nameof(MatrixElement.Value),
NumberOfIterations = 100,
NumberOfThreads = 1, // To eliminate randomness, # of threads must be 1.
ApproximationRank = 64,
LearningRate = 0.5,
};

var pipeline = mlContext.Recommendation().Trainers.MatrixFactorization(options);

// Train a matrix factorization model.
var model = pipeline.Fit(dataView);

// Check if the expected types in the trained model are expected.
Assert.True(model.MatrixColumnIndexColumnName == nameof(MatrixElementZeroBased256By256.MatrixColumnIndex));
Assert.True(model.MatrixRowIndexColumnName == nameof(MatrixElementZeroBased256By256.MatrixRowIndex));
var matColKeyType = model.MatrixColumnIndexColumnType as KeyDataViewType;
Assert.NotNull(matColKeyType);
var matRowKeyType = model.MatrixRowIndexColumnType as KeyDataViewType;
Assert.NotNull(matRowKeyType);
Assert.True(matColKeyType.Count == _matrixColumnCount);
Assert.True(matRowKeyType.Count == _matrixRowCount);

// Create a test set with assigning scores. It stands for the 2nd column of the training matrix.
var testMatrix = new List<MatrixElementZeroBasedForScore256By256>();
for (/* column index */ uint i = 1; i < 2; ++i)
for (/* row index */ uint j = 0; j < _matrixRowCount; ++j)
testMatrix.Add(new MatrixElementZeroBasedForScore256By256() { MatrixColumnIndex = i, MatrixRowIndex = j, Score = 0 });

// Load test set as IDataView.
var testData = ML.Data.LoadFromEnumerable(testMatrix);

// Apply the trained model to the training set
var transformedTestData = model.Transform(testData);

// Load back predictions on the 2nd column as IEnumerable<MatrixElementZeroBasedForScore>.
var predictions = mlContext.Data.CreateEnumerable<MatrixElementZeroBasedForScore256By256>(transformedTestData, false).ToList();

// Inspect the trained model.
int m = model.Model.NumberOfRows;
int n = model.Model.NumberOfColumns;
int k = model.Model.ApproximationRank;

// The training matrix is approximated by leftFactorMatrix * rightFactorMatrix^T, where "^T" means matrix transpose.
// Thus, to compute the approximation of the 2nd column, we only need the whole leftFactorMatrix and the 2nd row in rightFactorMatrix.

// First copy the trained left factor matrix to an aligned for applying SSE code.
var leftFactorMatrix = model.Model.LeftFactorMatrix;
var leftFactorMatrixAligned = new AlignedArray(m * k, 64);
for (int i = 0; i < leftFactorMatrix.Count; ++i)
leftFactorMatrixAligned[i] = leftFactorMatrix[i];

// Second copy the trained right factor row to a k-by-1 aligned vector for applying SSE code.
var rightFactorVectorAligned = new AlignedArray(k, 64);
for (int i = 0; i < k; ++i)
rightFactorVectorAligned[i] = model.Model.RightFactorMatrix[1 * k + i]; // value at the i-th row and j-th column is indexed by i * k + j.

// Prepare buffer to store result. The result will be a matrix-vector product, where the matrix is leftFactorMatrix
// and the vector is the 2nd row of rightFactorMatrix.
var valuesAtSecondColumn = new AlignedArray(m, 64);

// Compute leftFactorMatrixAligned (m-by-k) * rightFactorVectorAligned (k-by-1).
CpuMathUtils.MatrixTimesSource(false, leftFactorMatrixAligned, rightFactorVectorAligned, valuesAtSecondColumn, m);

// Check if results computed by SSE code and MF predictor are the same.
for (int i = 0; i < predictions.Count(); ++i)
Assert.Equal(predictions[i].Score, valuesAtSecondColumn[i], 3);
}
}
}