.NET data type system instead of DvTypes #673

Description

@codemzs

.NET data type system instead of DvTypes

Motivation

Machine Learning datasets often have missing values and to accommodate them along with C# native
types without increasing the memory footprint DvType system was created. If we were to use
Nullable<T> then we are looking at additional memory for HasValue boolean field plus another 3
bytes for 4 byte alignment. The C# native types that are replaced using DvTypes are bool as DvBool,
sbyte as DvInt1, int16 as DvInt2, int32 as DvInt4, int64 as DvInt8, DvDateTime as System.DateTime,
DvDateTimeZone as combination of DvDateTime and DvInt2 offset, DvTimeSpan as SysTimeSpan and string
as DvText. Float and Double types already have a special value called NaN that can be used for
missing value. DvType system achieves a smaller memory footprint by denoting special value for
missing value which is usually the smallest number that can be represented by the native type that
is encapsulated by DvType, example, DvInt1's missing value indicator would be SByte.MinValue and in
the case of types that represent date/time types it is a value that represent maximum ticks.

We plan to remove DvTypes to make IDataView a general commodity that can be used in other products
and for this to happen it would be nice if it did not having a dependency on a special type system.
If in future we find having DvTypes was useful then we can consider exposing it natively from .NET
platform. Once we remove DvTypes then ML.NET platform will be using native non-nullable C# types.
Float or double types can be used to represent missing value.

Column Types

Columns in ML.NET make up the dataset and ColumnType defines a column. At high level there are two
kinds of column, first is PrimitiveType and that comprises of types such as NumberType,
BoolType, TextType, DateTimeType, DateTimeZoneType, KeyType, second is Structured type
and it comparises of VectorType. ColumnType is primarily made up of Type and DataKind.
Type could refer to any type but it is instantiated with a type referred by DataKind which is an
identifer for data types that comprises of DvTypes, native C# types such as float, double and custom
big integer UInt128.

Type conversion

DvTypes have implicit and explicit override for assignment operator that handles type conversion.
Lets consider DvInt1 for example:

ToFromCurrent behavior
DvInt1sbyteCopy the value as it is
DvInt1sbyte?Assign missing value if null otherwise copy the value as it is
sbyteDvInt1Copy if not a missing value otherwise throw exception
sbyte?DvInt1Assign null for missing values otherwise copy over
DvInt1DvBoolAssign missing value for a missing value otherwise copy value over
DvInt1DvInt2Cast raw value from short to sbyte and compare it with original value if they are not same assign missing value otherwise casted value
DvInt1DvInt4Same as above
DvInt1DvInt8Same as above
DvInt1Float
DvInt1DoubleSame as above
FloatDvInt1Assign NaN for missing value
DoubleDvInt1Same as above

Similar conversion rules exist for DvInt2, DvInt4, DvInt8 and DvBool.

Logical, bitwise and numerical operators

Operations such as ==, !=, !, >, >=, <, <=, +,-,*,pow,|,& take place
between same DvTypes only. They also handle missing values and in the case of arithmetic operators
overflow is also handled. Most of these overrides are implemented but only few are actively used.
Whenever there is an overflow the resulting value is represented as missing value and the same goes
when one of the operands is a missing value.

Serialization

DvTypes have their own codecs for efficiently compressing data and writing it to disk, for example,
to write DvBool to disk, two bits are used to represent a boolean value, 0x00 is false, 0x01 is true
and 0x10 is missing value indicator. Boolean values are written at the level of int32 which has 32
bits that can accommodate 32/2 or 16 boolean values in 4 bytes as opposed to using 1 byte per
boolean value using the naive approach that does not even handle missing value. We can reuse this
approach to serialize bool by using one bit instead of two. DvInt* codecs need not be changed at
all. DateTime and DvText codecs will require some changes.

Intermediate Language(IL) code generation

ML.NET contains a mini compiler that generates IL code at runtime for peak and poke functions that
basically perform reflection of objects to set and get values in a more performant manner. Here we
can use OpCodes.Stobj to emit IL code for DvTimeSpan,DvDateTime, DvDateTimeZone and
ReadOnlyMemory<char> types.

New Behavior

  • DvInt1, DvInt2, DvInt4, DvInt8 will be replaced with sbyte, short, int and long
    respectively.

    • Conversions will conform to .NET standard conversions.
    • Types will be converted using casting and this might cause underflow and overflow and therefore
      behavior is undefined here, example, casting long to sbyte will result in assigning of low 8
      bits from long to sbyte. ML.NET projects by default are unchecked because checked is expensive
      and hence used in code blocks where it is needed.
      > unchecked((sbyte)long.MaxValue)
      -1
      
    • Conversion from Text to Integer type is done by first converting Text to long value in
      the case of positive number and ulong in the case of negative number and then validating this
      value is within the legal bounds of the type that it is being converted to from Text type,
      example, legal bound for sbyte is -128 to 127, so converting "-129" or "128" will result in an
      exception, also converting a value that is out of legal bounds for a long type will also
      result in an exception.
      var c = Convert.ToSByte("129");
      Value was either too large or too small for a signed byte.
      sbyte.Parse(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
      System.Convert.ToSByte(string)
      
  • DvTimeSpan, DvDateTime and DvDateTimeZone will be replaced with TimeSpan, DateTime and
    DateTimeOffset respectively.

    • Offset in DataTimeOffset is represented as long because it records the ticks. Previously this
      was represented as DvInt2 or short in DvDateTimeZone because it was recorded as minutes and
      due to this it had a smaller footprint on the disk. With offset being long the footprint will
      increase, one work around is to convert it to minutes before writing and then converting minutes
      back to ticks but this might lead to loss in precision. Since DataTime is very rarely used in
      Machine Learning so I'm not sure if it is worth making an optimization here.
  • DvText will be replaced with ReadOnlyMemory<char>.

    • ReadOnlyMemory<char> does not implement IEquatable<T> and due to this it cannot be be used a
      type in GroupKeyColumnChecker in Cursor in GroupTransform. The workaround for this is to
      remove the IEquatable<T> contraint on the type and instead use if else to check if the type
      implements IEquatable<T> then cast and call Equals method otherwise check if the type is of
      ReadOnlyMemory<char> then use its utility method for equality otherwise throw an exception.
    • ReadOnlyMemory<char> does not implement GetHashCode() and due to this it cannot be used as a
      key in a dictionary in ReconcileSlotNames<T> in EvaluatorUtils.cs. The workaround for this is
      to use string representation of ReadOnlyMemory<char> as a key. While this is wastage of memory
      but its not too bad because this is only used at the end of evaluation phase and the number of
      strings allocated here will be roughly proportional to the number of classes.
  • DvBool will be replaced with bool.

    • GetPredictedLabel and GetPredictedLabelCore will result in an undefined behavior in the case
      where score contains a missing value represented as NaN. Here we will default to false.
  • Backward compatiblity when reading IDV files written with DvTypes.

    • Integers are read as they were written to disk, i.e minimum value of the corresponding data
      type in the case of missing value.
    • Boolean is read using the old codec, where two bits are used per value and missing values are
      converted to false to fit in bool type.
    • DateTime, DateTimeSpan, DateTimeZone use long and short type underneath to represent
      ticks and offset and they are converted using the Integer scheme defined above. In the case
      where ticks or offset is read and found to contain missing value represented as a minimum of
      the underlying type then it is converted to default value of that type to prevent an exception
      from DateTime or TimeSpan or DateTimeOffset class as such minimum values indicate an
      invalid date.
    • DvText is read as it is. Missing values when being converted to Integer types are converted to
      minimum value of that integer type and empty string is converted to default value of that
      integer type.
  • TextLoader

    • Will throw an exception if it encounters missing value.
    • Will convert empty string to default values of type it is being converted to.
  • Parquet Loader

    • Will throw an exception for nullables or overflow.

Future consideration

Introduce an option in the loader whether to throw an exception in the case of missing value or just
replace them with default values. With the current design we will throw an exception in the case
of missing for Text Loader and Parquet loader but not IDV(Binary Loader).

Benchmarking the type system changes

(this section was written by @najeeb-kazmi )

ReadOnlyMemory<char> is a data type introduced recently that allows management of strings without unnecessary memory allocation. Strings in C# are immutable. Hence, when we take a string operation such as substring, the resulting string is copied to a new memory location. To prevent unnecessary allocation of memory, ReadOnlyMemory keeps track of the substring via start and end offsets relative to the original string. Hence, for every substring operation, the memory allocated is constant. In ReadOnlyMemory, if one needs to access independent elements, they do it by calling the Span property, which returns a ReadOnlySpan object, which is a stack only concept. It turns out that this Span property is an expensive operation, and our initial benchmarks showed that runtimes of the pipelines regressed by 100%. Upon further performance analysis, we decide to cache the returned ReadOnlySpan as much as we could, and that brought the runtimes on par with DvText.

These benchmarks are intended to compare performance after these optimizations on Span were done, in order to investigate whether we hit parity with DvText or not.

Datasets and pipelines

We chose datasets and pipelines to test to cover a variety of scenarios, including:

  • numeric data only
  • numeric + categorical data with categorical transform
  • numeric + categorical data with categorical and categorical hash transforms
  • categorical + text data with categorical and text transforms
  • text transform only on a very large text dataset

The table below shows the datasets and their characteristics, as well as the pipeline that we executed on each dataset. All datasets were ingested in text format, which makes heavy use of DvText / ReadOnlyMemory<char>. Other data types are also involved in the pipelines, although the performance of the pipelines are dominated by DvText / ReadOnlyMemory<char>.

DatasetSizeRowsFeaturesPipelineComments
Criteo230 MB1M13 numeric 26 categoricalTrain data={\ct01\data\Criteo\Kaggle\train-1M.txt} loader=TextLoader{ col=Label:R4:0 col=NumFeatures:R4:1-13 col=LowCardCat:TX:19,22,30,33 col=HighCardCat:TX:~ } xf=CategoricalTransform{col=LowCardCat} xf=CategoricalHashTransform{col=HighCardCat bits=16} xf=MissingValueIndicatorTransform{col=NumFeatures} xf=Concat{ col=Features:NumFeatures,LowCardCat,HighCardCat } tr=ap{iter=10} seed=1 cache=-Numeric + categorical features with categorical and categorical hash transforms
Bing Click Prediction3 GB500k3076 numericTrain data={\ct01\data\TeamOnly\NumericalDatasets\Ranking\BingClickPrediction\train-500K} loader=TextLoader{col=Label:R4:0 col=Features:R4:8-3083 header=+ quote=-} xf=NAHandleTransform{col=Features ind=-} tr=SDCA seed=1 cache=-Numeric features only
Flight Delay227 MB7M5 numeric 3 categoricalTrain data={\ct01\data\PerformanceAnalysis\Data\Flight\New\FD2007train.csv} loader=TextLoader{ sep=, col=Month:R4:0 col=DayofMonth:R4:1 col=DayofWeek:R4:2 col=DepTime:R4:3 col=Distance:R4:4 col=UniqueCarrier:TX:5 col=Origin:TX:6 col=Dest:TX:7 col=Label:R4:9 header=+ } xf=CategoricalTransform{ col=UniqueCarrier col=Origin col=Dest } xf=Concat{ col=Features:Month,DayofMonth,DayofWeek,DepTime,Distance,UniqueCarrier,Origin,Dest } tr=SDCA seed=1 cache=-Numeric + categorical features with categorical transform
Wikipedia Detox74 MB160k1 categorical 1 text columnTrain data={\ct01\data\SCRATCH_TO_MOVE\BinaryClassification\WikipediaDetox\toxicity_annotated_comments.merged.shuf-75MB,_160k-rows.tsv} loader=TextLoader{ quote=- sparse=- col=Label:R4:0 col=rev_id:TX:1 col=text:TX:2 col=year:TX:3 col=logged_in:BL:4 col=ns:TX:5 col=sample:TX:6 col=split:TX:7 header=+ } xf=Convert{col=logged_in type=R4} xf=CategoricalTransform{col=ns} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=FeaturesText:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } xf=Concat{col=Features:logged_in,ns,FeaturesText} tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Categorical transform + text featurization
Amazon Reviews9 GB18M1 text columnTrain data={\ct01\users\prroy\dataset\cleandata_VW\Amazon_reviews_cleaned.tsv} loader=TextLoader{col=Label:TX:0 col=text:TX:1 header=+ sparse=-} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=Features:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Text featurization on a very large dataset

Methodology and experimental setup

  • The two builds of ML.NET (one using DvTypes and the other using .NET data types) were built to target .NET Core 2.1.
  • Pipelines were executed from the Microsoft.ML.Console project: dotnet MML.dll <pipeline>
  • All pipelines were executed on Azure Standard F72s_v2 VMs running Windows Server 2016, which offer an instance isolated to dedicated hardware (Intel Xeon Platinum 8168).
  • We killed background processes that were not needed to run the experiments, including closing Visual Studio, ensuring that only one console window was open on the VM.
  • For each pipeline, we discarded the results of the first two runs for each pipeline to control for runtime variability due to a cold start, keeping only the subsequent runs for analysis.

Results

We present the results of the benchmarks here. The deltas indicate performance gap of .NET data types relative to DvTypes: negative values indicate slower performance of .NET data types compared to DvTypes, and percentage deltas are based off the mean runtime for DvTypes. Finally, we did an independent samples t-test with unequal variances for the two builds, and present the p-values for each test. We chose a significance threshold of 0.05, with a smaller p-value indicating significant differences.

We can see that for all the pipelines except the one with Amazon Reviews dataset, the deltas were within 1% of the speed of DvTypes, and were not significant. For Amazon Reviews, the delta was 1.85% of the speed of DvTypes and significant. The statistical significance is not particularly concerning here because the long runtimes on this dataset were bound to return significantly different runtimes even with a small percentage difference. More important thing here is that the performance gap was reduced from ~100% to within 2%. We expect the performance to only improve with further optimizations in future .NET Core runtimes.

Criteo 1M

Run #.NET data typesDvTypes
112.90712.634
212.63512.847
312.98912.546
412.70812.713
512.78912.463
612.56512.751
712.82812.73
812.68812.425
912.79113.009
1012.85812.584
Mean12.775812.6702
S.D.0.1287208870.178014232
Delta-0.1056-0.83%
p-value0.073767344Not significant

Flight Delay 7M

Run #.NET data typesDvTypes
152.53651.562
252.66752.501
352.17552.475
452.07651.773
554.1951.786
651.67852.698
752.64752.338
852.42652.704
951.70351.214
1051.74252.407
Mean52.38452.1458
S.D.0.741520.520013632
Delta-0.2382-0.46%
p-value0.208863Not significant

Bing Click Prediction 500K

Run #.NET data typesDvTypes
1222221
2222222
3220223
4221223
5220220
6223219
7222222
8223220
9223223
10222222
Mean221.8221.5
S.D.1.1352921.433721
Delta-0.3-0.14%
p-value0.305291Not significant

Wikipedia Detox

Run #.NET data typesDvTypes
165.99265.265
266.04265.308
365.667.457
465.14666.011
566.19665.788
665.68367.611
765.49865.191
865.81966.636
965.89665.412
1066.56466.381
1166.39266.074
1265.86265.155
1365.95864.808
1466.08565.157
1566.08566.116
1666.11666.189
1766.08665.748
1866.82266.066
1966.22765.009
2065.27865.911
Mean65.9673565.86465
S.D.0.4026670.758248
Delta-0.1027-0.16%
p-value0.29838Not significant

Amazon Reviews

Run #.NET data typesDvTypes
151214992
251215016
350905036
451634981
551125003
650755008
750975022
850934991
950715040
1050905019
Mean5103.35010.8
S.D.27.1008419.46393
Delta-92.5-1.85%
p-value7.05E-08Significant

CC: @eerhardt@Zruty0@Ivanidzo4ka@TomFinley@shauheen@najeeb-kazmi@markusweimer

Metadata

Metadata

Assignees

Labels

APIIssues pertaining the friendly API

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    .NET data type system instead of DvTypes #673

    Description

    @codemzs

    .NET data type system instead of DvTypes

    Motivation

    Machine Learning datasets often have missing values and to accommodate them along with C# native
    types without increasing the memory footprint DvType system was created. If we were to use
    Nullable<T> then we are looking at additional memory for HasValue boolean field plus another 3
    bytes for 4 byte alignment. The C# native types that are replaced using DvTypes are bool as DvBool,
    sbyte as DvInt1, int16 as DvInt2, int32 as DvInt4, int64 as DvInt8, DvDateTime as System.DateTime,
    DvDateTimeZone as combination of DvDateTime and DvInt2 offset, DvTimeSpan as SysTimeSpan and string
    as DvText. Float and Double types already have a special value called NaN that can be used for
    missing value. DvType system achieves a smaller memory footprint by denoting special value for
    missing value which is usually the smallest number that can be represented by the native type that
    is encapsulated by DvType, example, DvInt1's missing value indicator would be SByte.MinValue and in
    the case of types that represent date/time types it is a value that represent maximum ticks.

    We plan to remove DvTypes to make IDataView a general commodity that can be used in other products
    and for this to happen it would be nice if it did not having a dependency on a special type system.
    If in future we find having DvTypes was useful then we can consider exposing it natively from .NET
    platform. Once we remove DvTypes then ML.NET platform will be using native non-nullable C# types.
    Float or double types can be used to represent missing value.

    Column Types

    Columns in ML.NET make up the dataset and ColumnType defines a column. At high level there are two
    kinds of column, first is PrimitiveType and that comprises of types such as NumberType,
    BoolType, TextType, DateTimeType, DateTimeZoneType, KeyType, second is Structured type
    and it comparises of VectorType. ColumnType is primarily made up of Type and DataKind.
    Type could refer to any type but it is instantiated with a type referred by DataKind which is an
    identifer for data types that comprises of DvTypes, native C# types such as float, double and custom
    big integer UInt128.

    Type conversion

    DvTypes have implicit and explicit override for assignment operator that handles type conversion.
    Lets consider DvInt1 for example:

    ToFromCurrent behavior
    DvInt1sbyteCopy the value as it is
    DvInt1sbyte?Assign missing value if null otherwise copy the value as it is
    sbyteDvInt1Copy if not a missing value otherwise throw exception
    sbyte?DvInt1Assign null for missing values otherwise copy over
    DvInt1DvBoolAssign missing value for a missing value otherwise copy value over
    DvInt1DvInt2Cast raw value from short to sbyte and compare it with original value if they are not same assign missing value otherwise casted value
    DvInt1DvInt4Same as above
    DvInt1DvInt8Same as above
    DvInt1Float
    DvInt1DoubleSame as above
    FloatDvInt1Assign NaN for missing value
    DoubleDvInt1Same as above

    Similar conversion rules exist for DvInt2, DvInt4, DvInt8 and DvBool.

    Logical, bitwise and numerical operators

    Operations such as ==, !=, !, >, >=, <, <=, +,-,*,pow,|,& take place
    between same DvTypes only. They also handle missing values and in the case of arithmetic operators
    overflow is also handled. Most of these overrides are implemented but only few are actively used.
    Whenever there is an overflow the resulting value is represented as missing value and the same goes
    when one of the operands is a missing value.

    Serialization

    DvTypes have their own codecs for efficiently compressing data and writing it to disk, for example,
    to write DvBool to disk, two bits are used to represent a boolean value, 0x00 is false, 0x01 is true
    and 0x10 is missing value indicator. Boolean values are written at the level of int32 which has 32
    bits that can accommodate 32/2 or 16 boolean values in 4 bytes as opposed to using 1 byte per
    boolean value using the naive approach that does not even handle missing value. We can reuse this
    approach to serialize bool by using one bit instead of two. DvInt* codecs need not be changed at
    all. DateTime and DvText codecs will require some changes.

    Intermediate Language(IL) code generation

    ML.NET contains a mini compiler that generates IL code at runtime for peak and poke functions that
    basically perform reflection of objects to set and get values in a more performant manner. Here we
    can use OpCodes.Stobj to emit IL code for DvTimeSpan,DvDateTime, DvDateTimeZone and
    ReadOnlyMemory<char> types.

    New Behavior

    • DvInt1, DvInt2, DvInt4, DvInt8 will be replaced with sbyte, short, int and long
      respectively.

      • Conversions will conform to .NET standard conversions.
      • Types will be converted using casting and this might cause underflow and overflow and therefore
        behavior is undefined here, example, casting long to sbyte will result in assigning of low 8
        bits from long to sbyte. ML.NET projects by default are unchecked because checked is expensive
        and hence used in code blocks where it is needed.
        > unchecked((sbyte)long.MaxValue)
        -1
        
      • Conversion from Text to Integer type is done by first converting Text to long value in
        the case of positive number and ulong in the case of negative number and then validating this
        value is within the legal bounds of the type that it is being converted to from Text type,
        example, legal bound for sbyte is -128 to 127, so converting "-129" or "128" will result in an
        exception, also converting a value that is out of legal bounds for a long type will also
        result in an exception.
        var c = Convert.ToSByte("129");
        Value was either too large or too small for a signed byte.
        sbyte.Parse(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
        System.Convert.ToSByte(string)
        
    • DvTimeSpan, DvDateTime and DvDateTimeZone will be replaced with TimeSpan, DateTime and
      DateTimeOffset respectively.

      • Offset in DataTimeOffset is represented as long because it records the ticks. Previously this
        was represented as DvInt2 or short in DvDateTimeZone because it was recorded as minutes and
        due to this it had a smaller footprint on the disk. With offset being long the footprint will
        increase, one work around is to convert it to minutes before writing and then converting minutes
        back to ticks but this might lead to loss in precision. Since DataTime is very rarely used in
        Machine Learning so I'm not sure if it is worth making an optimization here.
    • DvText will be replaced with ReadOnlyMemory<char>.

      • ReadOnlyMemory<char> does not implement IEquatable<T> and due to this it cannot be be used a
        type in GroupKeyColumnChecker in Cursor in GroupTransform. The workaround for this is to
        remove the IEquatable<T> contraint on the type and instead use if else to check if the type
        implements IEquatable<T> then cast and call Equals method otherwise check if the type is of
        ReadOnlyMemory<char> then use its utility method for equality otherwise throw an exception.
      • ReadOnlyMemory<char> does not implement GetHashCode() and due to this it cannot be used as a
        key in a dictionary in ReconcileSlotNames<T> in EvaluatorUtils.cs. The workaround for this is
        to use string representation of ReadOnlyMemory<char> as a key. While this is wastage of memory
        but its not too bad because this is only used at the end of evaluation phase and the number of
        strings allocated here will be roughly proportional to the number of classes.
    • DvBool will be replaced with bool.

      • GetPredictedLabel and GetPredictedLabelCore will result in an undefined behavior in the case
        where score contains a missing value represented as NaN. Here we will default to false.
    • Backward compatiblity when reading IDV files written with DvTypes.

      • Integers are read as they were written to disk, i.e minimum value of the corresponding data
        type in the case of missing value.
      • Boolean is read using the old codec, where two bits are used per value and missing values are
        converted to false to fit in bool type.
      • DateTime, DateTimeSpan, DateTimeZone use long and short type underneath to represent
        ticks and offset and they are converted using the Integer scheme defined above. In the case
        where ticks or offset is read and found to contain missing value represented as a minimum of
        the underlying type then it is converted to default value of that type to prevent an exception
        from DateTime or TimeSpan or DateTimeOffset class as such minimum values indicate an
        invalid date.
      • DvText is read as it is. Missing values when being converted to Integer types are converted to
        minimum value of that integer type and empty string is converted to default value of that
        integer type.
    • TextLoader

      • Will throw an exception if it encounters missing value.
      • Will convert empty string to default values of type it is being converted to.
    • Parquet Loader

      • Will throw an exception for nullables or overflow.

    Future consideration

    Introduce an option in the loader whether to throw an exception in the case of missing value or just
    replace them with default values. With the current design we will throw an exception in the case
    of missing for Text Loader and Parquet loader but not IDV(Binary Loader).

    Benchmarking the type system changes

    (this section was written by @najeeb-kazmi )

    ReadOnlyMemory<char> is a data type introduced recently that allows management of strings without unnecessary memory allocation. Strings in C# are immutable. Hence, when we take a string operation such as substring, the resulting string is copied to a new memory location. To prevent unnecessary allocation of memory, ReadOnlyMemory keeps track of the substring via start and end offsets relative to the original string. Hence, for every substring operation, the memory allocated is constant. In ReadOnlyMemory, if one needs to access independent elements, they do it by calling the Span property, which returns a ReadOnlySpan object, which is a stack only concept. It turns out that this Span property is an expensive operation, and our initial benchmarks showed that runtimes of the pipelines regressed by 100%. Upon further performance analysis, we decide to cache the returned ReadOnlySpan as much as we could, and that brought the runtimes on par with DvText.

    These benchmarks are intended to compare performance after these optimizations on Span were done, in order to investigate whether we hit parity with DvText or not.

    Datasets and pipelines

    We chose datasets and pipelines to test to cover a variety of scenarios, including:

    • numeric data only
    • numeric + categorical data with categorical transform
    • numeric + categorical data with categorical and categorical hash transforms
    • categorical + text data with categorical and text transforms
    • text transform only on a very large text dataset

    The table below shows the datasets and their characteristics, as well as the pipeline that we executed on each dataset. All datasets were ingested in text format, which makes heavy use of DvText / ReadOnlyMemory<char>. Other data types are also involved in the pipelines, although the performance of the pipelines are dominated by DvText / ReadOnlyMemory<char>.

    DatasetSizeRowsFeaturesPipelineComments
    Criteo230 MB1M13 numeric 26 categoricalTrain data={\ct01\data\Criteo\Kaggle\train-1M.txt} loader=TextLoader{ col=Label:R4:0 col=NumFeatures:R4:1-13 col=LowCardCat:TX:19,22,30,33 col=HighCardCat:TX:~ } xf=CategoricalTransform{col=LowCardCat} xf=CategoricalHashTransform{col=HighCardCat bits=16} xf=MissingValueIndicatorTransform{col=NumFeatures} xf=Concat{ col=Features:NumFeatures,LowCardCat,HighCardCat } tr=ap{iter=10} seed=1 cache=-Numeric + categorical features with categorical and categorical hash transforms
    Bing Click Prediction3 GB500k3076 numericTrain data={\ct01\data\TeamOnly\NumericalDatasets\Ranking\BingClickPrediction\train-500K} loader=TextLoader{col=Label:R4:0 col=Features:R4:8-3083 header=+ quote=-} xf=NAHandleTransform{col=Features ind=-} tr=SDCA seed=1 cache=-Numeric features only
    Flight Delay227 MB7M5 numeric 3 categoricalTrain data={\ct01\data\PerformanceAnalysis\Data\Flight\New\FD2007train.csv} loader=TextLoader{ sep=, col=Month:R4:0 col=DayofMonth:R4:1 col=DayofWeek:R4:2 col=DepTime:R4:3 col=Distance:R4:4 col=UniqueCarrier:TX:5 col=Origin:TX:6 col=Dest:TX:7 col=Label:R4:9 header=+ } xf=CategoricalTransform{ col=UniqueCarrier col=Origin col=Dest } xf=Concat{ col=Features:Month,DayofMonth,DayofWeek,DepTime,Distance,UniqueCarrier,Origin,Dest } tr=SDCA seed=1 cache=-Numeric + categorical features with categorical transform
    Wikipedia Detox74 MB160k1 categorical 1 text columnTrain data={\ct01\data\SCRATCH_TO_MOVE\BinaryClassification\WikipediaDetox\toxicity_annotated_comments.merged.shuf-75MB,_160k-rows.tsv} loader=TextLoader{ quote=- sparse=- col=Label:R4:0 col=rev_id:TX:1 col=text:TX:2 col=year:TX:3 col=logged_in:BL:4 col=ns:TX:5 col=sample:TX:6 col=split:TX:7 header=+ } xf=Convert{col=logged_in type=R4} xf=CategoricalTransform{col=ns} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=FeaturesText:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } xf=Concat{col=Features:logged_in,ns,FeaturesText} tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Categorical transform + text featurization
    Amazon Reviews9 GB18M1 text columnTrain data={\ct01\users\prroy\dataset\cleandata_VW\Amazon_reviews_cleaned.tsv} loader=TextLoader{col=Label:TX:0 col=text:TX:1 header=+ sparse=-} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=Features:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Text featurization on a very large dataset

    Methodology and experimental setup

    • The two builds of ML.NET (one using DvTypes and the other using .NET data types) were built to target .NET Core 2.1.
    • Pipelines were executed from the Microsoft.ML.Console project: dotnet MML.dll <pipeline>
    • All pipelines were executed on Azure Standard F72s_v2 VMs running Windows Server 2016, which offer an instance isolated to dedicated hardware (Intel Xeon Platinum 8168).
    • We killed background processes that were not needed to run the experiments, including closing Visual Studio, ensuring that only one console window was open on the VM.
    • For each pipeline, we discarded the results of the first two runs for each pipeline to control for runtime variability due to a cold start, keeping only the subsequent runs for analysis.

    Results

    We present the results of the benchmarks here. The deltas indicate performance gap of .NET data types relative to DvTypes: negative values indicate slower performance of .NET data types compared to DvTypes, and percentage deltas are based off the mean runtime for DvTypes. Finally, we did an independent samples t-test with unequal variances for the two builds, and present the p-values for each test. We chose a significance threshold of 0.05, with a smaller p-value indicating significant differences.

    We can see that for all the pipelines except the one with Amazon Reviews dataset, the deltas were within 1% of the speed of DvTypes, and were not significant. For Amazon Reviews, the delta was 1.85% of the speed of DvTypes and significant. The statistical significance is not particularly concerning here because the long runtimes on this dataset were bound to return significantly different runtimes even with a small percentage difference. More important thing here is that the performance gap was reduced from ~100% to within 2%. We expect the performance to only improve with further optimizations in future .NET Core runtimes.

    Criteo 1M

    Run #.NET data typesDvTypes
    112.90712.634
    212.63512.847
    312.98912.546
    412.70812.713
    512.78912.463
    612.56512.751
    712.82812.73
    812.68812.425
    912.79113.009
    1012.85812.584
    Mean12.775812.6702
    S.D.0.1287208870.178014232
    Delta-0.1056-0.83%
    p-value0.073767344Not significant

    Flight Delay 7M

    Run #.NET data typesDvTypes
    152.53651.562
    252.66752.501
    352.17552.475
    452.07651.773
    554.1951.786
    651.67852.698
    752.64752.338
    852.42652.704
    951.70351.214
    1051.74252.407
    Mean52.38452.1458
    S.D.0.741520.520013632
    Delta-0.2382-0.46%
    p-value0.208863Not significant

    Bing Click Prediction 500K

    Run #.NET data typesDvTypes
    1222221
    2222222
    3220223
    4221223
    5220220
    6223219
    7222222
    8223220
    9223223
    10222222
    Mean221.8221.5
    S.D.1.1352921.433721
    Delta-0.3-0.14%
    p-value0.305291Not significant

    Wikipedia Detox

    Run #.NET data typesDvTypes
    165.99265.265
    266.04265.308
    365.667.457
    465.14666.011
    566.19665.788
    665.68367.611
    765.49865.191
    865.81966.636
    965.89665.412
    1066.56466.381
    1166.39266.074
    1265.86265.155
    1365.95864.808
    1466.08565.157
    1566.08566.116
    1666.11666.189
    1766.08665.748
    1866.82266.066
    1966.22765.009
    2065.27865.911
    Mean65.9673565.86465
    S.D.0.4026670.758248
    Delta-0.1027-0.16%
    p-value0.29838Not significant

    Amazon Reviews

    Run #.NET data typesDvTypes
    151214992
    251215016
    350905036
    451634981
    551125003
    650755008
    750975022
    850934991
    950715040
    1050905019
    Mean5103.35010.8
    S.D.27.1008419.46393
    Delta-92.5-1.85%
    p-value7.05E-08Significant

    CC: @eerhardt@Zruty0@Ivanidzo4ka@TomFinley@shauheen@najeeb-kazmi@markusweimer

    Metadata

    Metadata

    Assignees

    Labels

    APIIssues pertaining the friendly API

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      .NET data type system instead of DvTypes #673

      Description

      @codemzs

      .NET data type system instead of DvTypes

      Motivation

      Machine Learning datasets often have missing values and to accommodate them along with C# native
      types without increasing the memory footprint DvType system was created. If we were to use
      Nullable<T> then we are looking at additional memory for HasValue boolean field plus another 3
      bytes for 4 byte alignment. The C# native types that are replaced using DvTypes are bool as DvBool,
      sbyte as DvInt1, int16 as DvInt2, int32 as DvInt4, int64 as DvInt8, DvDateTime as System.DateTime,
      DvDateTimeZone as combination of DvDateTime and DvInt2 offset, DvTimeSpan as SysTimeSpan and string
      as DvText. Float and Double types already have a special value called NaN that can be used for
      missing value. DvType system achieves a smaller memory footprint by denoting special value for
      missing value which is usually the smallest number that can be represented by the native type that
      is encapsulated by DvType, example, DvInt1's missing value indicator would be SByte.MinValue and in
      the case of types that represent date/time types it is a value that represent maximum ticks.

      We plan to remove DvTypes to make IDataView a general commodity that can be used in other products
      and for this to happen it would be nice if it did not having a dependency on a special type system.
      If in future we find having DvTypes was useful then we can consider exposing it natively from .NET
      platform. Once we remove DvTypes then ML.NET platform will be using native non-nullable C# types.
      Float or double types can be used to represent missing value.

      Column Types

      Columns in ML.NET make up the dataset and ColumnType defines a column. At high level there are two
      kinds of column, first is PrimitiveType and that comprises of types such as NumberType,
      BoolType, TextType, DateTimeType, DateTimeZoneType, KeyType, second is Structured type
      and it comparises of VectorType. ColumnType is primarily made up of Type and DataKind.
      Type could refer to any type but it is instantiated with a type referred by DataKind which is an
      identifer for data types that comprises of DvTypes, native C# types such as float, double and custom
      big integer UInt128.

      Type conversion

      DvTypes have implicit and explicit override for assignment operator that handles type conversion.
      Lets consider DvInt1 for example:

      ToFromCurrent behavior
      DvInt1sbyteCopy the value as it is
      DvInt1sbyte?Assign missing value if null otherwise copy the value as it is
      sbyteDvInt1Copy if not a missing value otherwise throw exception
      sbyte?DvInt1Assign null for missing values otherwise copy over
      DvInt1DvBoolAssign missing value for a missing value otherwise copy value over
      DvInt1DvInt2Cast raw value from short to sbyte and compare it with original value if they are not same assign missing value otherwise casted value
      DvInt1DvInt4Same as above
      DvInt1DvInt8Same as above
      DvInt1Float
      DvInt1DoubleSame as above
      FloatDvInt1Assign NaN for missing value
      DoubleDvInt1Same as above

      Similar conversion rules exist for DvInt2, DvInt4, DvInt8 and DvBool.

      Logical, bitwise and numerical operators

      Operations such as ==, !=, !, >, >=, <, <=, +,-,*,pow,|,& take place
      between same DvTypes only. They also handle missing values and in the case of arithmetic operators
      overflow is also handled. Most of these overrides are implemented but only few are actively used.
      Whenever there is an overflow the resulting value is represented as missing value and the same goes
      when one of the operands is a missing value.

      Serialization

      DvTypes have their own codecs for efficiently compressing data and writing it to disk, for example,
      to write DvBool to disk, two bits are used to represent a boolean value, 0x00 is false, 0x01 is true
      and 0x10 is missing value indicator. Boolean values are written at the level of int32 which has 32
      bits that can accommodate 32/2 or 16 boolean values in 4 bytes as opposed to using 1 byte per
      boolean value using the naive approach that does not even handle missing value. We can reuse this
      approach to serialize bool by using one bit instead of two. DvInt* codecs need not be changed at
      all. DateTime and DvText codecs will require some changes.

      Intermediate Language(IL) code generation

      ML.NET contains a mini compiler that generates IL code at runtime for peak and poke functions that
      basically perform reflection of objects to set and get values in a more performant manner. Here we
      can use OpCodes.Stobj to emit IL code for DvTimeSpan,DvDateTime, DvDateTimeZone and
      ReadOnlyMemory<char> types.

      New Behavior

      • DvInt1, DvInt2, DvInt4, DvInt8 will be replaced with sbyte, short, int and long
        respectively.

        • Conversions will conform to .NET standard conversions.
        • Types will be converted using casting and this might cause underflow and overflow and therefore
          behavior is undefined here, example, casting long to sbyte will result in assigning of low 8
          bits from long to sbyte. ML.NET projects by default are unchecked because checked is expensive
          and hence used in code blocks where it is needed.
          > unchecked((sbyte)long.MaxValue)
          -1
          
        • Conversion from Text to Integer type is done by first converting Text to long value in
          the case of positive number and ulong in the case of negative number and then validating this
          value is within the legal bounds of the type that it is being converted to from Text type,
          example, legal bound for sbyte is -128 to 127, so converting "-129" or "128" will result in an
          exception, also converting a value that is out of legal bounds for a long type will also
          result in an exception.
          var c = Convert.ToSByte("129");
          Value was either too large or too small for a signed byte.
          sbyte.Parse(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
          System.Convert.ToSByte(string)
          
      • DvTimeSpan, DvDateTime and DvDateTimeZone will be replaced with TimeSpan, DateTime and
        DateTimeOffset respectively.

        • Offset in DataTimeOffset is represented as long because it records the ticks. Previously this
          was represented as DvInt2 or short in DvDateTimeZone because it was recorded as minutes and
          due to this it had a smaller footprint on the disk. With offset being long the footprint will
          increase, one work around is to convert it to minutes before writing and then converting minutes
          back to ticks but this might lead to loss in precision. Since DataTime is very rarely used in
          Machine Learning so I'm not sure if it is worth making an optimization here.
      • DvText will be replaced with ReadOnlyMemory<char>.

        • ReadOnlyMemory<char> does not implement IEquatable<T> and due to this it cannot be be used a
          type in GroupKeyColumnChecker in Cursor in GroupTransform. The workaround for this is to
          remove the IEquatable<T> contraint on the type and instead use if else to check if the type
          implements IEquatable<T> then cast and call Equals method otherwise check if the type is of
          ReadOnlyMemory<char> then use its utility method for equality otherwise throw an exception.
        • ReadOnlyMemory<char> does not implement GetHashCode() and due to this it cannot be used as a
          key in a dictionary in ReconcileSlotNames<T> in EvaluatorUtils.cs. The workaround for this is
          to use string representation of ReadOnlyMemory<char> as a key. While this is wastage of memory
          but its not too bad because this is only used at the end of evaluation phase and the number of
          strings allocated here will be roughly proportional to the number of classes.
      • DvBool will be replaced with bool.

        • GetPredictedLabel and GetPredictedLabelCore will result in an undefined behavior in the case
          where score contains a missing value represented as NaN. Here we will default to false.
      • Backward compatiblity when reading IDV files written with DvTypes.

        • Integers are read as they were written to disk, i.e minimum value of the corresponding data
          type in the case of missing value.
        • Boolean is read using the old codec, where two bits are used per value and missing values are
          converted to false to fit in bool type.
        • DateTime, DateTimeSpan, DateTimeZone use long and short type underneath to represent
          ticks and offset and they are converted using the Integer scheme defined above. In the case
          where ticks or offset is read and found to contain missing value represented as a minimum of
          the underlying type then it is converted to default value of that type to prevent an exception
          from DateTime or TimeSpan or DateTimeOffset class as such minimum values indicate an
          invalid date.
        • DvText is read as it is. Missing values when being converted to Integer types are converted to
          minimum value of that integer type and empty string is converted to default value of that
          integer type.
      • TextLoader

        • Will throw an exception if it encounters missing value.
        • Will convert empty string to default values of type it is being converted to.
      • Parquet Loader

        • Will throw an exception for nullables or overflow.

      Future consideration

      Introduce an option in the loader whether to throw an exception in the case of missing value or just
      replace them with default values. With the current design we will throw an exception in the case
      of missing for Text Loader and Parquet loader but not IDV(Binary Loader).

      Benchmarking the type system changes

      (this section was written by @najeeb-kazmi )

      ReadOnlyMemory<char> is a data type introduced recently that allows management of strings without unnecessary memory allocation. Strings in C# are immutable. Hence, when we take a string operation such as substring, the resulting string is copied to a new memory location. To prevent unnecessary allocation of memory, ReadOnlyMemory keeps track of the substring via start and end offsets relative to the original string. Hence, for every substring operation, the memory allocated is constant. In ReadOnlyMemory, if one needs to access independent elements, they do it by calling the Span property, which returns a ReadOnlySpan object, which is a stack only concept. It turns out that this Span property is an expensive operation, and our initial benchmarks showed that runtimes of the pipelines regressed by 100%. Upon further performance analysis, we decide to cache the returned ReadOnlySpan as much as we could, and that brought the runtimes on par with DvText.

      These benchmarks are intended to compare performance after these optimizations on Span were done, in order to investigate whether we hit parity with DvText or not.

      Datasets and pipelines

      We chose datasets and pipelines to test to cover a variety of scenarios, including:

      • numeric data only
      • numeric + categorical data with categorical transform
      • numeric + categorical data with categorical and categorical hash transforms
      • categorical + text data with categorical and text transforms
      • text transform only on a very large text dataset

      The table below shows the datasets and their characteristics, as well as the pipeline that we executed on each dataset. All datasets were ingested in text format, which makes heavy use of DvText / ReadOnlyMemory<char>. Other data types are also involved in the pipelines, although the performance of the pipelines are dominated by DvText / ReadOnlyMemory<char>.

      DatasetSizeRowsFeaturesPipelineComments
      Criteo230 MB1M13 numeric 26 categoricalTrain data={\ct01\data\Criteo\Kaggle\train-1M.txt} loader=TextLoader{ col=Label:R4:0 col=NumFeatures:R4:1-13 col=LowCardCat:TX:19,22,30,33 col=HighCardCat:TX:~ } xf=CategoricalTransform{col=LowCardCat} xf=CategoricalHashTransform{col=HighCardCat bits=16} xf=MissingValueIndicatorTransform{col=NumFeatures} xf=Concat{ col=Features:NumFeatures,LowCardCat,HighCardCat } tr=ap{iter=10} seed=1 cache=-Numeric + categorical features with categorical and categorical hash transforms
      Bing Click Prediction3 GB500k3076 numericTrain data={\ct01\data\TeamOnly\NumericalDatasets\Ranking\BingClickPrediction\train-500K} loader=TextLoader{col=Label:R4:0 col=Features:R4:8-3083 header=+ quote=-} xf=NAHandleTransform{col=Features ind=-} tr=SDCA seed=1 cache=-Numeric features only
      Flight Delay227 MB7M5 numeric 3 categoricalTrain data={\ct01\data\PerformanceAnalysis\Data\Flight\New\FD2007train.csv} loader=TextLoader{ sep=, col=Month:R4:0 col=DayofMonth:R4:1 col=DayofWeek:R4:2 col=DepTime:R4:3 col=Distance:R4:4 col=UniqueCarrier:TX:5 col=Origin:TX:6 col=Dest:TX:7 col=Label:R4:9 header=+ } xf=CategoricalTransform{ col=UniqueCarrier col=Origin col=Dest } xf=Concat{ col=Features:Month,DayofMonth,DayofWeek,DepTime,Distance,UniqueCarrier,Origin,Dest } tr=SDCA seed=1 cache=-Numeric + categorical features with categorical transform
      Wikipedia Detox74 MB160k1 categorical 1 text columnTrain data={\ct01\data\SCRATCH_TO_MOVE\BinaryClassification\WikipediaDetox\toxicity_annotated_comments.merged.shuf-75MB,_160k-rows.tsv} loader=TextLoader{ quote=- sparse=- col=Label:R4:0 col=rev_id:TX:1 col=text:TX:2 col=year:TX:3 col=logged_in:BL:4 col=ns:TX:5 col=sample:TX:6 col=split:TX:7 header=+ } xf=Convert{col=logged_in type=R4} xf=CategoricalTransform{col=ns} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=FeaturesText:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } xf=Concat{col=Features:logged_in,ns,FeaturesText} tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Categorical transform + text featurization
      Amazon Reviews9 GB18M1 text columnTrain data={\ct01\users\prroy\dataset\cleandata_VW\Amazon_reviews_cleaned.tsv} loader=TextLoader{col=Label:TX:0 col=text:TX:1 header=+ sparse=-} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=Features:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Text featurization on a very large dataset

      Methodology and experimental setup

      • The two builds of ML.NET (one using DvTypes and the other using .NET data types) were built to target .NET Core 2.1.
      • Pipelines were executed from the Microsoft.ML.Console project: dotnet MML.dll <pipeline>
      • All pipelines were executed on Azure Standard F72s_v2 VMs running Windows Server 2016, which offer an instance isolated to dedicated hardware (Intel Xeon Platinum 8168).
      • We killed background processes that were not needed to run the experiments, including closing Visual Studio, ensuring that only one console window was open on the VM.
      • For each pipeline, we discarded the results of the first two runs for each pipeline to control for runtime variability due to a cold start, keeping only the subsequent runs for analysis.

      Results

      We present the results of the benchmarks here. The deltas indicate performance gap of .NET data types relative to DvTypes: negative values indicate slower performance of .NET data types compared to DvTypes, and percentage deltas are based off the mean runtime for DvTypes. Finally, we did an independent samples t-test with unequal variances for the two builds, and present the p-values for each test. We chose a significance threshold of 0.05, with a smaller p-value indicating significant differences.

      We can see that for all the pipelines except the one with Amazon Reviews dataset, the deltas were within 1% of the speed of DvTypes, and were not significant. For Amazon Reviews, the delta was 1.85% of the speed of DvTypes and significant. The statistical significance is not particularly concerning here because the long runtimes on this dataset were bound to return significantly different runtimes even with a small percentage difference. More important thing here is that the performance gap was reduced from ~100% to within 2%. We expect the performance to only improve with further optimizations in future .NET Core runtimes.

      Criteo 1M

      Run #.NET data typesDvTypes
      112.90712.634
      212.63512.847
      312.98912.546
      412.70812.713
      512.78912.463
      612.56512.751
      712.82812.73
      812.68812.425
      912.79113.009
      1012.85812.584
      Mean12.775812.6702
      S.D.0.1287208870.178014232
      Delta-0.1056-0.83%
      p-value0.073767344Not significant

      Flight Delay 7M

      Run #.NET data typesDvTypes
      152.53651.562
      252.66752.501
      352.17552.475
      452.07651.773
      554.1951.786
      651.67852.698
      752.64752.338
      852.42652.704
      951.70351.214
      1051.74252.407
      Mean52.38452.1458
      S.D.0.741520.520013632
      Delta-0.2382-0.46%
      p-value0.208863Not significant

      Bing Click Prediction 500K

      Run #.NET data typesDvTypes
      1222221
      2222222
      3220223
      4221223
      5220220
      6223219
      7222222
      8223220
      9223223
      10222222
      Mean221.8221.5
      S.D.1.1352921.433721
      Delta-0.3-0.14%
      p-value0.305291Not significant

      Wikipedia Detox

      Run #.NET data typesDvTypes
      165.99265.265
      266.04265.308
      365.667.457
      465.14666.011
      566.19665.788
      665.68367.611
      765.49865.191
      865.81966.636
      965.89665.412
      1066.56466.381
      1166.39266.074
      1265.86265.155
      1365.95864.808
      1466.08565.157
      1566.08566.116
      1666.11666.189
      1766.08665.748
      1866.82266.066
      1966.22765.009
      2065.27865.911
      Mean65.9673565.86465
      S.D.0.4026670.758248
      Delta-0.1027-0.16%
      p-value0.29838Not significant

      Amazon Reviews

      Run #.NET data typesDvTypes
      151214992
      251215016
      350905036
      451634981
      551125003
      650755008
      750975022
      850934991
      950715040
      1050905019
      Mean5103.35010.8
      S.D.27.1008419.46393
      Delta-92.5-1.85%
      p-value7.05E-08Significant

      CC: @eerhardt@Zruty0@Ivanidzo4ka@TomFinley@shauheen@najeeb-kazmi@markusweimer

      Metadata

      Metadata

      Assignees

      Labels

      APIIssues pertaining the friendly API

      Type

      No type

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        .NET data type system instead of DvTypes #673

        Description

        @codemzs

        .NET data type system instead of DvTypes

        Motivation

        Machine Learning datasets often have missing values and to accommodate them along with C# native
        types without increasing the memory footprint DvType system was created. If we were to use
        Nullable<T> then we are looking at additional memory for HasValue boolean field plus another 3
        bytes for 4 byte alignment. The C# native types that are replaced using DvTypes are bool as DvBool,
        sbyte as DvInt1, int16 as DvInt2, int32 as DvInt4, int64 as DvInt8, DvDateTime as System.DateTime,
        DvDateTimeZone as combination of DvDateTime and DvInt2 offset, DvTimeSpan as SysTimeSpan and string
        as DvText. Float and Double types already have a special value called NaN that can be used for
        missing value. DvType system achieves a smaller memory footprint by denoting special value for
        missing value which is usually the smallest number that can be represented by the native type that
        is encapsulated by DvType, example, DvInt1's missing value indicator would be SByte.MinValue and in
        the case of types that represent date/time types it is a value that represent maximum ticks.

        We plan to remove DvTypes to make IDataView a general commodity that can be used in other products
        and for this to happen it would be nice if it did not having a dependency on a special type system.
        If in future we find having DvTypes was useful then we can consider exposing it natively from .NET
        platform. Once we remove DvTypes then ML.NET platform will be using native non-nullable C# types.
        Float or double types can be used to represent missing value.

        Column Types

        Columns in ML.NET make up the dataset and ColumnType defines a column. At high level there are two
        kinds of column, first is PrimitiveType and that comprises of types such as NumberType,
        BoolType, TextType, DateTimeType, DateTimeZoneType, KeyType, second is Structured type
        and it comparises of VectorType. ColumnType is primarily made up of Type and DataKind.
        Type could refer to any type but it is instantiated with a type referred by DataKind which is an
        identifer for data types that comprises of DvTypes, native C# types such as float, double and custom
        big integer UInt128.

        Type conversion

        DvTypes have implicit and explicit override for assignment operator that handles type conversion.
        Lets consider DvInt1 for example:

        ToFromCurrent behavior
        DvInt1sbyteCopy the value as it is
        DvInt1sbyte?Assign missing value if null otherwise copy the value as it is
        sbyteDvInt1Copy if not a missing value otherwise throw exception
        sbyte?DvInt1Assign null for missing values otherwise copy over
        DvInt1DvBoolAssign missing value for a missing value otherwise copy value over
        DvInt1DvInt2Cast raw value from short to sbyte and compare it with original value if they are not same assign missing value otherwise casted value
        DvInt1DvInt4Same as above
        DvInt1DvInt8Same as above
        DvInt1Float
        DvInt1DoubleSame as above
        FloatDvInt1Assign NaN for missing value
        DoubleDvInt1Same as above

        Similar conversion rules exist for DvInt2, DvInt4, DvInt8 and DvBool.

        Logical, bitwise and numerical operators

        Operations such as ==, !=, !, >, >=, <, <=, +,-,*,pow,|,& take place
        between same DvTypes only. They also handle missing values and in the case of arithmetic operators
        overflow is also handled. Most of these overrides are implemented but only few are actively used.
        Whenever there is an overflow the resulting value is represented as missing value and the same goes
        when one of the operands is a missing value.

        Serialization

        DvTypes have their own codecs for efficiently compressing data and writing it to disk, for example,
        to write DvBool to disk, two bits are used to represent a boolean value, 0x00 is false, 0x01 is true
        and 0x10 is missing value indicator. Boolean values are written at the level of int32 which has 32
        bits that can accommodate 32/2 or 16 boolean values in 4 bytes as opposed to using 1 byte per
        boolean value using the naive approach that does not even handle missing value. We can reuse this
        approach to serialize bool by using one bit instead of two. DvInt* codecs need not be changed at
        all. DateTime and DvText codecs will require some changes.

        Intermediate Language(IL) code generation

        ML.NET contains a mini compiler that generates IL code at runtime for peak and poke functions that
        basically perform reflection of objects to set and get values in a more performant manner. Here we
        can use OpCodes.Stobj to emit IL code for DvTimeSpan,DvDateTime, DvDateTimeZone and
        ReadOnlyMemory<char> types.

        New Behavior

        • DvInt1, DvInt2, DvInt4, DvInt8 will be replaced with sbyte, short, int and long
          respectively.

          • Conversions will conform to .NET standard conversions.
          • Types will be converted using casting and this might cause underflow and overflow and therefore
            behavior is undefined here, example, casting long to sbyte will result in assigning of low 8
            bits from long to sbyte. ML.NET projects by default are unchecked because checked is expensive
            and hence used in code blocks where it is needed.
            > unchecked((sbyte)long.MaxValue)
            -1
            
          • Conversion from Text to Integer type is done by first converting Text to long value in
            the case of positive number and ulong in the case of negative number and then validating this
            value is within the legal bounds of the type that it is being converted to from Text type,
            example, legal bound for sbyte is -128 to 127, so converting "-129" or "128" will result in an
            exception, also converting a value that is out of legal bounds for a long type will also
            result in an exception.
            var c = Convert.ToSByte("129");
            Value was either too large or too small for a signed byte.
            sbyte.Parse(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
            System.Convert.ToSByte(string)
            
        • DvTimeSpan, DvDateTime and DvDateTimeZone will be replaced with TimeSpan, DateTime and
          DateTimeOffset respectively.

          • Offset in DataTimeOffset is represented as long because it records the ticks. Previously this
            was represented as DvInt2 or short in DvDateTimeZone because it was recorded as minutes and
            due to this it had a smaller footprint on the disk. With offset being long the footprint will
            increase, one work around is to convert it to minutes before writing and then converting minutes
            back to ticks but this might lead to loss in precision. Since DataTime is very rarely used in
            Machine Learning so I'm not sure if it is worth making an optimization here.
        • DvText will be replaced with ReadOnlyMemory<char>.

          • ReadOnlyMemory<char> does not implement IEquatable<T> and due to this it cannot be be used a
            type in GroupKeyColumnChecker in Cursor in GroupTransform. The workaround for this is to
            remove the IEquatable<T> contraint on the type and instead use if else to check if the type
            implements IEquatable<T> then cast and call Equals method otherwise check if the type is of
            ReadOnlyMemory<char> then use its utility method for equality otherwise throw an exception.
          • ReadOnlyMemory<char> does not implement GetHashCode() and due to this it cannot be used as a
            key in a dictionary in ReconcileSlotNames<T> in EvaluatorUtils.cs. The workaround for this is
            to use string representation of ReadOnlyMemory<char> as a key. While this is wastage of memory
            but its not too bad because this is only used at the end of evaluation phase and the number of
            strings allocated here will be roughly proportional to the number of classes.
        • DvBool will be replaced with bool.

          • GetPredictedLabel and GetPredictedLabelCore will result in an undefined behavior in the case
            where score contains a missing value represented as NaN. Here we will default to false.
        • Backward compatiblity when reading IDV files written with DvTypes.

          • Integers are read as they were written to disk, i.e minimum value of the corresponding data
            type in the case of missing value.
          • Boolean is read using the old codec, where two bits are used per value and missing values are
            converted to false to fit in bool type.
          • DateTime, DateTimeSpan, DateTimeZone use long and short type underneath to represent
            ticks and offset and they are converted using the Integer scheme defined above. In the case
            where ticks or offset is read and found to contain missing value represented as a minimum of
            the underlying type then it is converted to default value of that type to prevent an exception
            from DateTime or TimeSpan or DateTimeOffset class as such minimum values indicate an
            invalid date.
          • DvText is read as it is. Missing values when being converted to Integer types are converted to
            minimum value of that integer type and empty string is converted to default value of that
            integer type.
        • TextLoader

          • Will throw an exception if it encounters missing value.
          • Will convert empty string to default values of type it is being converted to.
        • Parquet Loader

          • Will throw an exception for nullables or overflow.

        Future consideration

        Introduce an option in the loader whether to throw an exception in the case of missing value or just
        replace them with default values. With the current design we will throw an exception in the case
        of missing for Text Loader and Parquet loader but not IDV(Binary Loader).

        Benchmarking the type system changes

        (this section was written by @najeeb-kazmi )

        ReadOnlyMemory<char> is a data type introduced recently that allows management of strings without unnecessary memory allocation. Strings in C# are immutable. Hence, when we take a string operation such as substring, the resulting string is copied to a new memory location. To prevent unnecessary allocation of memory, ReadOnlyMemory keeps track of the substring via start and end offsets relative to the original string. Hence, for every substring operation, the memory allocated is constant. In ReadOnlyMemory, if one needs to access independent elements, they do it by calling the Span property, which returns a ReadOnlySpan object, which is a stack only concept. It turns out that this Span property is an expensive operation, and our initial benchmarks showed that runtimes of the pipelines regressed by 100%. Upon further performance analysis, we decide to cache the returned ReadOnlySpan as much as we could, and that brought the runtimes on par with DvText.

        These benchmarks are intended to compare performance after these optimizations on Span were done, in order to investigate whether we hit parity with DvText or not.

        Datasets and pipelines

        We chose datasets and pipelines to test to cover a variety of scenarios, including:

        • numeric data only
        • numeric + categorical data with categorical transform
        • numeric + categorical data with categorical and categorical hash transforms
        • categorical + text data with categorical and text transforms
        • text transform only on a very large text dataset

        The table below shows the datasets and their characteristics, as well as the pipeline that we executed on each dataset. All datasets were ingested in text format, which makes heavy use of DvText / ReadOnlyMemory<char>. Other data types are also involved in the pipelines, although the performance of the pipelines are dominated by DvText / ReadOnlyMemory<char>.

        DatasetSizeRowsFeaturesPipelineComments
        Criteo230 MB1M13 numeric 26 categoricalTrain data={\ct01\data\Criteo\Kaggle\train-1M.txt} loader=TextLoader{ col=Label:R4:0 col=NumFeatures:R4:1-13 col=LowCardCat:TX:19,22,30,33 col=HighCardCat:TX:~ } xf=CategoricalTransform{col=LowCardCat} xf=CategoricalHashTransform{col=HighCardCat bits=16} xf=MissingValueIndicatorTransform{col=NumFeatures} xf=Concat{ col=Features:NumFeatures,LowCardCat,HighCardCat } tr=ap{iter=10} seed=1 cache=-Numeric + categorical features with categorical and categorical hash transforms
        Bing Click Prediction3 GB500k3076 numericTrain data={\ct01\data\TeamOnly\NumericalDatasets\Ranking\BingClickPrediction\train-500K} loader=TextLoader{col=Label:R4:0 col=Features:R4:8-3083 header=+ quote=-} xf=NAHandleTransform{col=Features ind=-} tr=SDCA seed=1 cache=-Numeric features only
        Flight Delay227 MB7M5 numeric 3 categoricalTrain data={\ct01\data\PerformanceAnalysis\Data\Flight\New\FD2007train.csv} loader=TextLoader{ sep=, col=Month:R4:0 col=DayofMonth:R4:1 col=DayofWeek:R4:2 col=DepTime:R4:3 col=Distance:R4:4 col=UniqueCarrier:TX:5 col=Origin:TX:6 col=Dest:TX:7 col=Label:R4:9 header=+ } xf=CategoricalTransform{ col=UniqueCarrier col=Origin col=Dest } xf=Concat{ col=Features:Month,DayofMonth,DayofWeek,DepTime,Distance,UniqueCarrier,Origin,Dest } tr=SDCA seed=1 cache=-Numeric + categorical features with categorical transform
        Wikipedia Detox74 MB160k1 categorical 1 text columnTrain data={\ct01\data\SCRATCH_TO_MOVE\BinaryClassification\WikipediaDetox\toxicity_annotated_comments.merged.shuf-75MB,_160k-rows.tsv} loader=TextLoader{ quote=- sparse=- col=Label:R4:0 col=rev_id:TX:1 col=text:TX:2 col=year:TX:3 col=logged_in:BL:4 col=ns:TX:5 col=sample:TX:6 col=split:TX:7 header=+ } xf=Convert{col=logged_in type=R4} xf=CategoricalTransform{col=ns} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=FeaturesText:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } xf=Concat{col=Features:logged_in,ns,FeaturesText} tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Categorical transform + text featurization
        Amazon Reviews9 GB18M1 text columnTrain data={\ct01\users\prroy\dataset\cleandata_VW\Amazon_reviews_cleaned.tsv} loader=TextLoader{col=Label:TX:0 col=text:TX:1 header=+ sparse=-} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=Features:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Text featurization on a very large dataset

        Methodology and experimental setup

        • The two builds of ML.NET (one using DvTypes and the other using .NET data types) were built to target .NET Core 2.1.
        • Pipelines were executed from the Microsoft.ML.Console project: dotnet MML.dll <pipeline>
        • All pipelines were executed on Azure Standard F72s_v2 VMs running Windows Server 2016, which offer an instance isolated to dedicated hardware (Intel Xeon Platinum 8168).
        • We killed background processes that were not needed to run the experiments, including closing Visual Studio, ensuring that only one console window was open on the VM.
        • For each pipeline, we discarded the results of the first two runs for each pipeline to control for runtime variability due to a cold start, keeping only the subsequent runs for analysis.

        Results

        We present the results of the benchmarks here. The deltas indicate performance gap of .NET data types relative to DvTypes: negative values indicate slower performance of .NET data types compared to DvTypes, and percentage deltas are based off the mean runtime for DvTypes. Finally, we did an independent samples t-test with unequal variances for the two builds, and present the p-values for each test. We chose a significance threshold of 0.05, with a smaller p-value indicating significant differences.

        We can see that for all the pipelines except the one with Amazon Reviews dataset, the deltas were within 1% of the speed of DvTypes, and were not significant. For Amazon Reviews, the delta was 1.85% of the speed of DvTypes and significant. The statistical significance is not particularly concerning here because the long runtimes on this dataset were bound to return significantly different runtimes even with a small percentage difference. More important thing here is that the performance gap was reduced from ~100% to within 2%. We expect the performance to only improve with further optimizations in future .NET Core runtimes.

        Criteo 1M

        Run #.NET data typesDvTypes
        112.90712.634
        212.63512.847
        312.98912.546
        412.70812.713
        512.78912.463
        612.56512.751
        712.82812.73
        812.68812.425
        912.79113.009
        1012.85812.584
        Mean12.775812.6702
        S.D.0.1287208870.178014232
        Delta-0.1056-0.83%
        p-value0.073767344Not significant

        Flight Delay 7M

        Run #.NET data typesDvTypes
        152.53651.562
        252.66752.501
        352.17552.475
        452.07651.773
        554.1951.786
        651.67852.698
        752.64752.338
        852.42652.704
        951.70351.214
        1051.74252.407
        Mean52.38452.1458
        S.D.0.741520.520013632
        Delta-0.2382-0.46%
        p-value0.208863Not significant

        Bing Click Prediction 500K

        Run #.NET data typesDvTypes
        1222221
        2222222
        3220223
        4221223
        5220220
        6223219
        7222222
        8223220
        9223223
        10222222
        Mean221.8221.5
        S.D.1.1352921.433721
        Delta-0.3-0.14%
        p-value0.305291Not significant

        Wikipedia Detox

        Run #.NET data typesDvTypes
        165.99265.265
        266.04265.308
        365.667.457
        465.14666.011
        566.19665.788
        665.68367.611
        765.49865.191
        865.81966.636
        965.89665.412
        1066.56466.381
        1166.39266.074
        1265.86265.155
        1365.95864.808
        1466.08565.157
        1566.08566.116
        1666.11666.189
        1766.08665.748
        1866.82266.066
        1966.22765.009
        2065.27865.911
        Mean65.9673565.86465
        S.D.0.4026670.758248
        Delta-0.1027-0.16%
        p-value0.29838Not significant

        Amazon Reviews

        Run #.NET data typesDvTypes
        151214992
        251215016
        350905036
        451634981
        551125003
        650755008
        750975022
        850934991
        950715040
        1050905019
        Mean5103.35010.8
        S.D.27.1008419.46393
        Delta-92.5-1.85%
        p-value7.05E-08Significant

        CC: @eerhardt@Zruty0@Ivanidzo4ka@TomFinley@shauheen@najeeb-kazmi@markusweimer

        Metadata

        Metadata

        Assignees

        Labels

        APIIssues pertaining the friendly API

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          .NET data type system instead of DvTypes #673

          Description

          @codemzs

          .NET data type system instead of DvTypes

          Motivation

          Machine Learning datasets often have missing values and to accommodate them along with C# native
          types without increasing the memory footprint DvType system was created. If we were to use
          Nullable<T> then we are looking at additional memory for HasValue boolean field plus another 3
          bytes for 4 byte alignment. The C# native types that are replaced using DvTypes are bool as DvBool,
          sbyte as DvInt1, int16 as DvInt2, int32 as DvInt4, int64 as DvInt8, DvDateTime as System.DateTime,
          DvDateTimeZone as combination of DvDateTime and DvInt2 offset, DvTimeSpan as SysTimeSpan and string
          as DvText. Float and Double types already have a special value called NaN that can be used for
          missing value. DvType system achieves a smaller memory footprint by denoting special value for
          missing value which is usually the smallest number that can be represented by the native type that
          is encapsulated by DvType, example, DvInt1's missing value indicator would be SByte.MinValue and in
          the case of types that represent date/time types it is a value that represent maximum ticks.

          We plan to remove DvTypes to make IDataView a general commodity that can be used in other products
          and for this to happen it would be nice if it did not having a dependency on a special type system.
          If in future we find having DvTypes was useful then we can consider exposing it natively from .NET
          platform. Once we remove DvTypes then ML.NET platform will be using native non-nullable C# types.
          Float or double types can be used to represent missing value.

          Column Types

          Columns in ML.NET make up the dataset and ColumnType defines a column. At high level there are two
          kinds of column, first is PrimitiveType and that comprises of types such as NumberType,
          BoolType, TextType, DateTimeType, DateTimeZoneType, KeyType, second is Structured type
          and it comparises of VectorType. ColumnType is primarily made up of Type and DataKind.
          Type could refer to any type but it is instantiated with a type referred by DataKind which is an
          identifer for data types that comprises of DvTypes, native C# types such as float, double and custom
          big integer UInt128.

          Type conversion

          DvTypes have implicit and explicit override for assignment operator that handles type conversion.
          Lets consider DvInt1 for example:

          ToFromCurrent behavior
          DvInt1sbyteCopy the value as it is
          DvInt1sbyte?Assign missing value if null otherwise copy the value as it is
          sbyteDvInt1Copy if not a missing value otherwise throw exception
          sbyte?DvInt1Assign null for missing values otherwise copy over
          DvInt1DvBoolAssign missing value for a missing value otherwise copy value over
          DvInt1DvInt2Cast raw value from short to sbyte and compare it with original value if they are not same assign missing value otherwise casted value
          DvInt1DvInt4Same as above
          DvInt1DvInt8Same as above
          DvInt1Float
          DvInt1DoubleSame as above
          FloatDvInt1Assign NaN for missing value
          DoubleDvInt1Same as above

          Similar conversion rules exist for DvInt2, DvInt4, DvInt8 and DvBool.

          Logical, bitwise and numerical operators

          Operations such as ==, !=, !, >, >=, <, <=, +,-,*,pow,|,& take place
          between same DvTypes only. They also handle missing values and in the case of arithmetic operators
          overflow is also handled. Most of these overrides are implemented but only few are actively used.
          Whenever there is an overflow the resulting value is represented as missing value and the same goes
          when one of the operands is a missing value.

          Serialization

          DvTypes have their own codecs for efficiently compressing data and writing it to disk, for example,
          to write DvBool to disk, two bits are used to represent a boolean value, 0x00 is false, 0x01 is true
          and 0x10 is missing value indicator. Boolean values are written at the level of int32 which has 32
          bits that can accommodate 32/2 or 16 boolean values in 4 bytes as opposed to using 1 byte per
          boolean value using the naive approach that does not even handle missing value. We can reuse this
          approach to serialize bool by using one bit instead of two. DvInt* codecs need not be changed at
          all. DateTime and DvText codecs will require some changes.

          Intermediate Language(IL) code generation

          ML.NET contains a mini compiler that generates IL code at runtime for peak and poke functions that
          basically perform reflection of objects to set and get values in a more performant manner. Here we
          can use OpCodes.Stobj to emit IL code for DvTimeSpan,DvDateTime, DvDateTimeZone and
          ReadOnlyMemory<char> types.

          New Behavior

          • DvInt1, DvInt2, DvInt4, DvInt8 will be replaced with sbyte, short, int and long
            respectively.

            • Conversions will conform to .NET standard conversions.
            • Types will be converted using casting and this might cause underflow and overflow and therefore
              behavior is undefined here, example, casting long to sbyte will result in assigning of low 8
              bits from long to sbyte. ML.NET projects by default are unchecked because checked is expensive
              and hence used in code blocks where it is needed.
              > unchecked((sbyte)long.MaxValue)
              -1
              
            • Conversion from Text to Integer type is done by first converting Text to long value in
              the case of positive number and ulong in the case of negative number and then validating this
              value is within the legal bounds of the type that it is being converted to from Text type,
              example, legal bound for sbyte is -128 to 127, so converting "-129" or "128" will result in an
              exception, also converting a value that is out of legal bounds for a long type will also
              result in an exception.
              var c = Convert.ToSByte("129");
              Value was either too large or too small for a signed byte.
              sbyte.Parse(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
              System.Convert.ToSByte(string)
              
          • DvTimeSpan, DvDateTime and DvDateTimeZone will be replaced with TimeSpan, DateTime and
            DateTimeOffset respectively.

            • Offset in DataTimeOffset is represented as long because it records the ticks. Previously this
              was represented as DvInt2 or short in DvDateTimeZone because it was recorded as minutes and
              due to this it had a smaller footprint on the disk. With offset being long the footprint will
              increase, one work around is to convert it to minutes before writing and then converting minutes
              back to ticks but this might lead to loss in precision. Since DataTime is very rarely used in
              Machine Learning so I'm not sure if it is worth making an optimization here.
          • DvText will be replaced with ReadOnlyMemory<char>.

            • ReadOnlyMemory<char> does not implement IEquatable<T> and due to this it cannot be be used a
              type in GroupKeyColumnChecker in Cursor in GroupTransform. The workaround for this is to
              remove the IEquatable<T> contraint on the type and instead use if else to check if the type
              implements IEquatable<T> then cast and call Equals method otherwise check if the type is of
              ReadOnlyMemory<char> then use its utility method for equality otherwise throw an exception.
            • ReadOnlyMemory<char> does not implement GetHashCode() and due to this it cannot be used as a
              key in a dictionary in ReconcileSlotNames<T> in EvaluatorUtils.cs. The workaround for this is
              to use string representation of ReadOnlyMemory<char> as a key. While this is wastage of memory
              but its not too bad because this is only used at the end of evaluation phase and the number of
              strings allocated here will be roughly proportional to the number of classes.
          • DvBool will be replaced with bool.

            • GetPredictedLabel and GetPredictedLabelCore will result in an undefined behavior in the case
              where score contains a missing value represented as NaN. Here we will default to false.
          • Backward compatiblity when reading IDV files written with DvTypes.

            • Integers are read as they were written to disk, i.e minimum value of the corresponding data
              type in the case of missing value.
            • Boolean is read using the old codec, where two bits are used per value and missing values are
              converted to false to fit in bool type.
            • DateTime, DateTimeSpan, DateTimeZone use long and short type underneath to represent
              ticks and offset and they are converted using the Integer scheme defined above. In the case
              where ticks or offset is read and found to contain missing value represented as a minimum of
              the underlying type then it is converted to default value of that type to prevent an exception
              from DateTime or TimeSpan or DateTimeOffset class as such minimum values indicate an
              invalid date.
            • DvText is read as it is. Missing values when being converted to Integer types are converted to
              minimum value of that integer type and empty string is converted to default value of that
              integer type.
          • TextLoader

            • Will throw an exception if it encounters missing value.
            • Will convert empty string to default values of type it is being converted to.
          • Parquet Loader

            • Will throw an exception for nullables or overflow.

          Future consideration

          Introduce an option in the loader whether to throw an exception in the case of missing value or just
          replace them with default values. With the current design we will throw an exception in the case
          of missing for Text Loader and Parquet loader but not IDV(Binary Loader).

          Benchmarking the type system changes

          (this section was written by @najeeb-kazmi )

          ReadOnlyMemory<char> is a data type introduced recently that allows management of strings without unnecessary memory allocation. Strings in C# are immutable. Hence, when we take a string operation such as substring, the resulting string is copied to a new memory location. To prevent unnecessary allocation of memory, ReadOnlyMemory keeps track of the substring via start and end offsets relative to the original string. Hence, for every substring operation, the memory allocated is constant. In ReadOnlyMemory, if one needs to access independent elements, they do it by calling the Span property, which returns a ReadOnlySpan object, which is a stack only concept. It turns out that this Span property is an expensive operation, and our initial benchmarks showed that runtimes of the pipelines regressed by 100%. Upon further performance analysis, we decide to cache the returned ReadOnlySpan as much as we could, and that brought the runtimes on par with DvText.

          These benchmarks are intended to compare performance after these optimizations on Span were done, in order to investigate whether we hit parity with DvText or not.

          Datasets and pipelines

          We chose datasets and pipelines to test to cover a variety of scenarios, including:

          • numeric data only
          • numeric + categorical data with categorical transform
          • numeric + categorical data with categorical and categorical hash transforms
          • categorical + text data with categorical and text transforms
          • text transform only on a very large text dataset

          The table below shows the datasets and their characteristics, as well as the pipeline that we executed on each dataset. All datasets were ingested in text format, which makes heavy use of DvText / ReadOnlyMemory<char>. Other data types are also involved in the pipelines, although the performance of the pipelines are dominated by DvText / ReadOnlyMemory<char>.

          DatasetSizeRowsFeaturesPipelineComments
          Criteo230 MB1M13 numeric 26 categoricalTrain data={\ct01\data\Criteo\Kaggle\train-1M.txt} loader=TextLoader{ col=Label:R4:0 col=NumFeatures:R4:1-13 col=LowCardCat:TX:19,22,30,33 col=HighCardCat:TX:~ } xf=CategoricalTransform{col=LowCardCat} xf=CategoricalHashTransform{col=HighCardCat bits=16} xf=MissingValueIndicatorTransform{col=NumFeatures} xf=Concat{ col=Features:NumFeatures,LowCardCat,HighCardCat } tr=ap{iter=10} seed=1 cache=-Numeric + categorical features with categorical and categorical hash transforms
          Bing Click Prediction3 GB500k3076 numericTrain data={\ct01\data\TeamOnly\NumericalDatasets\Ranking\BingClickPrediction\train-500K} loader=TextLoader{col=Label:R4:0 col=Features:R4:8-3083 header=+ quote=-} xf=NAHandleTransform{col=Features ind=-} tr=SDCA seed=1 cache=-Numeric features only
          Flight Delay227 MB7M5 numeric 3 categoricalTrain data={\ct01\data\PerformanceAnalysis\Data\Flight\New\FD2007train.csv} loader=TextLoader{ sep=, col=Month:R4:0 col=DayofMonth:R4:1 col=DayofWeek:R4:2 col=DepTime:R4:3 col=Distance:R4:4 col=UniqueCarrier:TX:5 col=Origin:TX:6 col=Dest:TX:7 col=Label:R4:9 header=+ } xf=CategoricalTransform{ col=UniqueCarrier col=Origin col=Dest } xf=Concat{ col=Features:Month,DayofMonth,DayofWeek,DepTime,Distance,UniqueCarrier,Origin,Dest } tr=SDCA seed=1 cache=-Numeric + categorical features with categorical transform
          Wikipedia Detox74 MB160k1 categorical 1 text columnTrain data={\ct01\data\SCRATCH_TO_MOVE\BinaryClassification\WikipediaDetox\toxicity_annotated_comments.merged.shuf-75MB,_160k-rows.tsv} loader=TextLoader{ quote=- sparse=- col=Label:R4:0 col=rev_id:TX:1 col=text:TX:2 col=year:TX:3 col=logged_in:BL:4 col=ns:TX:5 col=sample:TX:6 col=split:TX:7 header=+ } xf=Convert{col=logged_in type=R4} xf=CategoricalTransform{col=ns} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=FeaturesText:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } xf=Concat{col=Features:logged_in,ns,FeaturesText} tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Categorical transform + text featurization
          Amazon Reviews9 GB18M1 text columnTrain data={\ct01\users\prroy\dataset\cleandata_VW\Amazon_reviews_cleaned.tsv} loader=TextLoader{col=Label:TX:0 col=text:TX:1 header=+ sparse=-} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=Features:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Text featurization on a very large dataset

          Methodology and experimental setup

          • The two builds of ML.NET (one using DvTypes and the other using .NET data types) were built to target .NET Core 2.1.
          • Pipelines were executed from the Microsoft.ML.Console project: dotnet MML.dll <pipeline>
          • All pipelines were executed on Azure Standard F72s_v2 VMs running Windows Server 2016, which offer an instance isolated to dedicated hardware (Intel Xeon Platinum 8168).
          • We killed background processes that were not needed to run the experiments, including closing Visual Studio, ensuring that only one console window was open on the VM.
          • For each pipeline, we discarded the results of the first two runs for each pipeline to control for runtime variability due to a cold start, keeping only the subsequent runs for analysis.

          Results

          We present the results of the benchmarks here. The deltas indicate performance gap of .NET data types relative to DvTypes: negative values indicate slower performance of .NET data types compared to DvTypes, and percentage deltas are based off the mean runtime for DvTypes. Finally, we did an independent samples t-test with unequal variances for the two builds, and present the p-values for each test. We chose a significance threshold of 0.05, with a smaller p-value indicating significant differences.

          We can see that for all the pipelines except the one with Amazon Reviews dataset, the deltas were within 1% of the speed of DvTypes, and were not significant. For Amazon Reviews, the delta was 1.85% of the speed of DvTypes and significant. The statistical significance is not particularly concerning here because the long runtimes on this dataset were bound to return significantly different runtimes even with a small percentage difference. More important thing here is that the performance gap was reduced from ~100% to within 2%. We expect the performance to only improve with further optimizations in future .NET Core runtimes.

          Criteo 1M

          Run #.NET data typesDvTypes
          112.90712.634
          212.63512.847
          312.98912.546
          412.70812.713
          512.78912.463
          612.56512.751
          712.82812.73
          812.68812.425
          912.79113.009
          1012.85812.584
          Mean12.775812.6702
          S.D.0.1287208870.178014232
          Delta-0.1056-0.83%
          p-value0.073767344Not significant

          Flight Delay 7M

          Run #.NET data typesDvTypes
          152.53651.562
          252.66752.501
          352.17552.475
          452.07651.773
          554.1951.786
          651.67852.698
          752.64752.338
          852.42652.704
          951.70351.214
          1051.74252.407
          Mean52.38452.1458
          S.D.0.741520.520013632
          Delta-0.2382-0.46%
          p-value0.208863Not significant

          Bing Click Prediction 500K

          Run #.NET data typesDvTypes
          1222221
          2222222
          3220223
          4221223
          5220220
          6223219
          7222222
          8223220
          9223223
          10222222
          Mean221.8221.5
          S.D.1.1352921.433721
          Delta-0.3-0.14%
          p-value0.305291Not significant

          Wikipedia Detox

          Run #.NET data typesDvTypes
          165.99265.265
          266.04265.308
          365.667.457
          465.14666.011
          566.19665.788
          665.68367.611
          765.49865.191
          865.81966.636
          965.89665.412
          1066.56466.381
          1166.39266.074
          1265.86265.155
          1365.95864.808
          1466.08565.157
          1566.08566.116
          1666.11666.189
          1766.08665.748
          1866.82266.066
          1966.22765.009
          2065.27865.911
          Mean65.9673565.86465
          S.D.0.4026670.758248
          Delta-0.1027-0.16%
          p-value0.29838Not significant

          Amazon Reviews

          Run #.NET data typesDvTypes
          151214992
          251215016
          350905036
          451634981
          551125003
          650755008
          750975022
          850934991
          950715040
          1050905019
          Mean5103.35010.8
          S.D.27.1008419.46393
          Delta-92.5-1.85%
          p-value7.05E-08Significant

          CC: @eerhardt@Zruty0@Ivanidzo4ka@TomFinley@shauheen@najeeb-kazmi@markusweimer

          Metadata

          Metadata

          Assignees

          Labels

          APIIssues pertaining the friendly API

          Type

          No type

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            .NET data type system instead of DvTypes #673

            Description

            @codemzs

            .NET data type system instead of DvTypes

            Motivation

            Machine Learning datasets often have missing values and to accommodate them along with C# native
            types without increasing the memory footprint DvType system was created. If we were to use
            Nullable<T> then we are looking at additional memory for HasValue boolean field plus another 3
            bytes for 4 byte alignment. The C# native types that are replaced using DvTypes are bool as DvBool,
            sbyte as DvInt1, int16 as DvInt2, int32 as DvInt4, int64 as DvInt8, DvDateTime as System.DateTime,
            DvDateTimeZone as combination of DvDateTime and DvInt2 offset, DvTimeSpan as SysTimeSpan and string
            as DvText. Float and Double types already have a special value called NaN that can be used for
            missing value. DvType system achieves a smaller memory footprint by denoting special value for
            missing value which is usually the smallest number that can be represented by the native type that
            is encapsulated by DvType, example, DvInt1's missing value indicator would be SByte.MinValue and in
            the case of types that represent date/time types it is a value that represent maximum ticks.

            We plan to remove DvTypes to make IDataView a general commodity that can be used in other products
            and for this to happen it would be nice if it did not having a dependency on a special type system.
            If in future we find having DvTypes was useful then we can consider exposing it natively from .NET
            platform. Once we remove DvTypes then ML.NET platform will be using native non-nullable C# types.
            Float or double types can be used to represent missing value.

            Column Types

            Columns in ML.NET make up the dataset and ColumnType defines a column. At high level there are two
            kinds of column, first is PrimitiveType and that comprises of types such as NumberType,
            BoolType, TextType, DateTimeType, DateTimeZoneType, KeyType, second is Structured type
            and it comparises of VectorType. ColumnType is primarily made up of Type and DataKind.
            Type could refer to any type but it is instantiated with a type referred by DataKind which is an
            identifer for data types that comprises of DvTypes, native C# types such as float, double and custom
            big integer UInt128.

            Type conversion

            DvTypes have implicit and explicit override for assignment operator that handles type conversion.
            Lets consider DvInt1 for example:

            ToFromCurrent behavior
            DvInt1sbyteCopy the value as it is
            DvInt1sbyte?Assign missing value if null otherwise copy the value as it is
            sbyteDvInt1Copy if not a missing value otherwise throw exception
            sbyte?DvInt1Assign null for missing values otherwise copy over
            DvInt1DvBoolAssign missing value for a missing value otherwise copy value over
            DvInt1DvInt2Cast raw value from short to sbyte and compare it with original value if they are not same assign missing value otherwise casted value
            DvInt1DvInt4Same as above
            DvInt1DvInt8Same as above
            DvInt1Float
            DvInt1DoubleSame as above
            FloatDvInt1Assign NaN for missing value
            DoubleDvInt1Same as above

            Similar conversion rules exist for DvInt2, DvInt4, DvInt8 and DvBool.

            Logical, bitwise and numerical operators

            Operations such as ==, !=, !, >, >=, <, <=, +,-,*,pow,|,& take place
            between same DvTypes only. They also handle missing values and in the case of arithmetic operators
            overflow is also handled. Most of these overrides are implemented but only few are actively used.
            Whenever there is an overflow the resulting value is represented as missing value and the same goes
            when one of the operands is a missing value.

            Serialization

            DvTypes have their own codecs for efficiently compressing data and writing it to disk, for example,
            to write DvBool to disk, two bits are used to represent a boolean value, 0x00 is false, 0x01 is true
            and 0x10 is missing value indicator. Boolean values are written at the level of int32 which has 32
            bits that can accommodate 32/2 or 16 boolean values in 4 bytes as opposed to using 1 byte per
            boolean value using the naive approach that does not even handle missing value. We can reuse this
            approach to serialize bool by using one bit instead of two. DvInt* codecs need not be changed at
            all. DateTime and DvText codecs will require some changes.

            Intermediate Language(IL) code generation

            ML.NET contains a mini compiler that generates IL code at runtime for peak and poke functions that
            basically perform reflection of objects to set and get values in a more performant manner. Here we
            can use OpCodes.Stobj to emit IL code for DvTimeSpan,DvDateTime, DvDateTimeZone and
            ReadOnlyMemory<char> types.

            New Behavior

            • DvInt1, DvInt2, DvInt4, DvInt8 will be replaced with sbyte, short, int and long
              respectively.

              • Conversions will conform to .NET standard conversions.
              • Types will be converted using casting and this might cause underflow and overflow and therefore
                behavior is undefined here, example, casting long to sbyte will result in assigning of low 8
                bits from long to sbyte. ML.NET projects by default are unchecked because checked is expensive
                and hence used in code blocks where it is needed.
                > unchecked((sbyte)long.MaxValue)
                -1
                
              • Conversion from Text to Integer type is done by first converting Text to long value in
                the case of positive number and ulong in the case of negative number and then validating this
                value is within the legal bounds of the type that it is being converted to from Text type,
                example, legal bound for sbyte is -128 to 127, so converting "-129" or "128" will result in an
                exception, also converting a value that is out of legal bounds for a long type will also
                result in an exception.
                var c = Convert.ToSByte("129");
                Value was either too large or too small for a signed byte.
                sbyte.Parse(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
                System.Convert.ToSByte(string)
                
            • DvTimeSpan, DvDateTime and DvDateTimeZone will be replaced with TimeSpan, DateTime and
              DateTimeOffset respectively.

              • Offset in DataTimeOffset is represented as long because it records the ticks. Previously this
                was represented as DvInt2 or short in DvDateTimeZone because it was recorded as minutes and
                due to this it had a smaller footprint on the disk. With offset being long the footprint will
                increase, one work around is to convert it to minutes before writing and then converting minutes
                back to ticks but this might lead to loss in precision. Since DataTime is very rarely used in
                Machine Learning so I'm not sure if it is worth making an optimization here.
            • DvText will be replaced with ReadOnlyMemory<char>.

              • ReadOnlyMemory<char> does not implement IEquatable<T> and due to this it cannot be be used a
                type in GroupKeyColumnChecker in Cursor in GroupTransform. The workaround for this is to
                remove the IEquatable<T> contraint on the type and instead use if else to check if the type
                implements IEquatable<T> then cast and call Equals method otherwise check if the type is of
                ReadOnlyMemory<char> then use its utility method for equality otherwise throw an exception.
              • ReadOnlyMemory<char> does not implement GetHashCode() and due to this it cannot be used as a
                key in a dictionary in ReconcileSlotNames<T> in EvaluatorUtils.cs. The workaround for this is
                to use string representation of ReadOnlyMemory<char> as a key. While this is wastage of memory
                but its not too bad because this is only used at the end of evaluation phase and the number of
                strings allocated here will be roughly proportional to the number of classes.
            • DvBool will be replaced with bool.

              • GetPredictedLabel and GetPredictedLabelCore will result in an undefined behavior in the case
                where score contains a missing value represented as NaN. Here we will default to false.
            • Backward compatiblity when reading IDV files written with DvTypes.

              • Integers are read as they were written to disk, i.e minimum value of the corresponding data
                type in the case of missing value.
              • Boolean is read using the old codec, where two bits are used per value and missing values are
                converted to false to fit in bool type.
              • DateTime, DateTimeSpan, DateTimeZone use long and short type underneath to represent
                ticks and offset and they are converted using the Integer scheme defined above. In the case
                where ticks or offset is read and found to contain missing value represented as a minimum of
                the underlying type then it is converted to default value of that type to prevent an exception
                from DateTime or TimeSpan or DateTimeOffset class as such minimum values indicate an
                invalid date.
              • DvText is read as it is. Missing values when being converted to Integer types are converted to
                minimum value of that integer type and empty string is converted to default value of that
                integer type.
            • TextLoader

              • Will throw an exception if it encounters missing value.
              • Will convert empty string to default values of type it is being converted to.
            • Parquet Loader

              • Will throw an exception for nullables or overflow.

            Future consideration

            Introduce an option in the loader whether to throw an exception in the case of missing value or just
            replace them with default values. With the current design we will throw an exception in the case
            of missing for Text Loader and Parquet loader but not IDV(Binary Loader).

            Benchmarking the type system changes

            (this section was written by @najeeb-kazmi )

            ReadOnlyMemory<char> is a data type introduced recently that allows management of strings without unnecessary memory allocation. Strings in C# are immutable. Hence, when we take a string operation such as substring, the resulting string is copied to a new memory location. To prevent unnecessary allocation of memory, ReadOnlyMemory keeps track of the substring via start and end offsets relative to the original string. Hence, for every substring operation, the memory allocated is constant. In ReadOnlyMemory, if one needs to access independent elements, they do it by calling the Span property, which returns a ReadOnlySpan object, which is a stack only concept. It turns out that this Span property is an expensive operation, and our initial benchmarks showed that runtimes of the pipelines regressed by 100%. Upon further performance analysis, we decide to cache the returned ReadOnlySpan as much as we could, and that brought the runtimes on par with DvText.

            These benchmarks are intended to compare performance after these optimizations on Span were done, in order to investigate whether we hit parity with DvText or not.

            Datasets and pipelines

            We chose datasets and pipelines to test to cover a variety of scenarios, including:

            • numeric data only
            • numeric + categorical data with categorical transform
            • numeric + categorical data with categorical and categorical hash transforms
            • categorical + text data with categorical and text transforms
            • text transform only on a very large text dataset

            The table below shows the datasets and their characteristics, as well as the pipeline that we executed on each dataset. All datasets were ingested in text format, which makes heavy use of DvText / ReadOnlyMemory<char>. Other data types are also involved in the pipelines, although the performance of the pipelines are dominated by DvText / ReadOnlyMemory<char>.

            DatasetSizeRowsFeaturesPipelineComments
            Criteo230 MB1M13 numeric 26 categoricalTrain data={\ct01\data\Criteo\Kaggle\train-1M.txt} loader=TextLoader{ col=Label:R4:0 col=NumFeatures:R4:1-13 col=LowCardCat:TX:19,22,30,33 col=HighCardCat:TX:~ } xf=CategoricalTransform{col=LowCardCat} xf=CategoricalHashTransform{col=HighCardCat bits=16} xf=MissingValueIndicatorTransform{col=NumFeatures} xf=Concat{ col=Features:NumFeatures,LowCardCat,HighCardCat } tr=ap{iter=10} seed=1 cache=-Numeric + categorical features with categorical and categorical hash transforms
            Bing Click Prediction3 GB500k3076 numericTrain data={\ct01\data\TeamOnly\NumericalDatasets\Ranking\BingClickPrediction\train-500K} loader=TextLoader{col=Label:R4:0 col=Features:R4:8-3083 header=+ quote=-} xf=NAHandleTransform{col=Features ind=-} tr=SDCA seed=1 cache=-Numeric features only
            Flight Delay227 MB7M5 numeric 3 categoricalTrain data={\ct01\data\PerformanceAnalysis\Data\Flight\New\FD2007train.csv} loader=TextLoader{ sep=, col=Month:R4:0 col=DayofMonth:R4:1 col=DayofWeek:R4:2 col=DepTime:R4:3 col=Distance:R4:4 col=UniqueCarrier:TX:5 col=Origin:TX:6 col=Dest:TX:7 col=Label:R4:9 header=+ } xf=CategoricalTransform{ col=UniqueCarrier col=Origin col=Dest } xf=Concat{ col=Features:Month,DayofMonth,DayofWeek,DepTime,Distance,UniqueCarrier,Origin,Dest } tr=SDCA seed=1 cache=-Numeric + categorical features with categorical transform
            Wikipedia Detox74 MB160k1 categorical 1 text columnTrain data={\ct01\data\SCRATCH_TO_MOVE\BinaryClassification\WikipediaDetox\toxicity_annotated_comments.merged.shuf-75MB,_160k-rows.tsv} loader=TextLoader{ quote=- sparse=- col=Label:R4:0 col=rev_id:TX:1 col=text:TX:2 col=year:TX:3 col=logged_in:BL:4 col=ns:TX:5 col=sample:TX:6 col=split:TX:7 header=+ } xf=Convert{col=logged_in type=R4} xf=CategoricalTransform{col=ns} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=FeaturesText:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } xf=Concat{col=Features:logged_in,ns,FeaturesText} tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Categorical transform + text featurization
            Amazon Reviews9 GB18M1 text columnTrain data={\ct01\users\prroy\dataset\cleandata_VW\Amazon_reviews_cleaned.tsv} loader=TextLoader{col=Label:TX:0 col=text:TX:1 header=+ sparse=-} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=Features:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Text featurization on a very large dataset

            Methodology and experimental setup

            • The two builds of ML.NET (one using DvTypes and the other using .NET data types) were built to target .NET Core 2.1.
            • Pipelines were executed from the Microsoft.ML.Console project: dotnet MML.dll <pipeline>
            • All pipelines were executed on Azure Standard F72s_v2 VMs running Windows Server 2016, which offer an instance isolated to dedicated hardware (Intel Xeon Platinum 8168).
            • We killed background processes that were not needed to run the experiments, including closing Visual Studio, ensuring that only one console window was open on the VM.
            • For each pipeline, we discarded the results of the first two runs for each pipeline to control for runtime variability due to a cold start, keeping only the subsequent runs for analysis.

            Results

            We present the results of the benchmarks here. The deltas indicate performance gap of .NET data types relative to DvTypes: negative values indicate slower performance of .NET data types compared to DvTypes, and percentage deltas are based off the mean runtime for DvTypes. Finally, we did an independent samples t-test with unequal variances for the two builds, and present the p-values for each test. We chose a significance threshold of 0.05, with a smaller p-value indicating significant differences.

            We can see that for all the pipelines except the one with Amazon Reviews dataset, the deltas were within 1% of the speed of DvTypes, and were not significant. For Amazon Reviews, the delta was 1.85% of the speed of DvTypes and significant. The statistical significance is not particularly concerning here because the long runtimes on this dataset were bound to return significantly different runtimes even with a small percentage difference. More important thing here is that the performance gap was reduced from ~100% to within 2%. We expect the performance to only improve with further optimizations in future .NET Core runtimes.

            Criteo 1M

            Run #.NET data typesDvTypes
            112.90712.634
            212.63512.847
            312.98912.546
            412.70812.713
            512.78912.463
            612.56512.751
            712.82812.73
            812.68812.425
            912.79113.009
            1012.85812.584
            Mean12.775812.6702
            S.D.0.1287208870.178014232
            Delta-0.1056-0.83%
            p-value0.073767344Not significant

            Flight Delay 7M

            Run #.NET data typesDvTypes
            152.53651.562
            252.66752.501
            352.17552.475
            452.07651.773
            554.1951.786
            651.67852.698
            752.64752.338
            852.42652.704
            951.70351.214
            1051.74252.407
            Mean52.38452.1458
            S.D.0.741520.520013632
            Delta-0.2382-0.46%
            p-value0.208863Not significant

            Bing Click Prediction 500K

            Run #.NET data typesDvTypes
            1222221
            2222222
            3220223
            4221223
            5220220
            6223219
            7222222
            8223220
            9223223
            10222222
            Mean221.8221.5
            S.D.1.1352921.433721
            Delta-0.3-0.14%
            p-value0.305291Not significant

            Wikipedia Detox

            Run #.NET data typesDvTypes
            165.99265.265
            266.04265.308
            365.667.457
            465.14666.011
            566.19665.788
            665.68367.611
            765.49865.191
            865.81966.636
            965.89665.412
            1066.56466.381
            1166.39266.074
            1265.86265.155
            1365.95864.808
            1466.08565.157
            1566.08566.116
            1666.11666.189
            1766.08665.748
            1866.82266.066
            1966.22765.009
            2065.27865.911
            Mean65.9673565.86465
            S.D.0.4026670.758248
            Delta-0.1027-0.16%
            p-value0.29838Not significant

            Amazon Reviews

            Run #.NET data typesDvTypes
            151214992
            251215016
            350905036
            451634981
            551125003
            650755008
            750975022
            850934991
            950715040
            1050905019
            Mean5103.35010.8
            S.D.27.1008419.46393
            Delta-92.5-1.85%
            p-value7.05E-08Significant

            CC: @eerhardt@Zruty0@Ivanidzo4ka@TomFinley@shauheen@najeeb-kazmi@markusweimer

            Metadata

            Metadata

            Assignees

            Labels

            APIIssues pertaining the friendly API

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              .NET data type system instead of DvTypes #673

              Description

              @codemzs

              .NET data type system instead of DvTypes

              Motivation

              Machine Learning datasets often have missing values and to accommodate them along with C# native
              types without increasing the memory footprint DvType system was created. If we were to use
              Nullable<T> then we are looking at additional memory for HasValue boolean field plus another 3
              bytes for 4 byte alignment. The C# native types that are replaced using DvTypes are bool as DvBool,
              sbyte as DvInt1, int16 as DvInt2, int32 as DvInt4, int64 as DvInt8, DvDateTime as System.DateTime,
              DvDateTimeZone as combination of DvDateTime and DvInt2 offset, DvTimeSpan as SysTimeSpan and string
              as DvText. Float and Double types already have a special value called NaN that can be used for
              missing value. DvType system achieves a smaller memory footprint by denoting special value for
              missing value which is usually the smallest number that can be represented by the native type that
              is encapsulated by DvType, example, DvInt1's missing value indicator would be SByte.MinValue and in
              the case of types that represent date/time types it is a value that represent maximum ticks.

              We plan to remove DvTypes to make IDataView a general commodity that can be used in other products
              and for this to happen it would be nice if it did not having a dependency on a special type system.
              If in future we find having DvTypes was useful then we can consider exposing it natively from .NET
              platform. Once we remove DvTypes then ML.NET platform will be using native non-nullable C# types.
              Float or double types can be used to represent missing value.

              Column Types

              Columns in ML.NET make up the dataset and ColumnType defines a column. At high level there are two
              kinds of column, first is PrimitiveType and that comprises of types such as NumberType,
              BoolType, TextType, DateTimeType, DateTimeZoneType, KeyType, second is Structured type
              and it comparises of VectorType. ColumnType is primarily made up of Type and DataKind.
              Type could refer to any type but it is instantiated with a type referred by DataKind which is an
              identifer for data types that comprises of DvTypes, native C# types such as float, double and custom
              big integer UInt128.

              Type conversion

              DvTypes have implicit and explicit override for assignment operator that handles type conversion.
              Lets consider DvInt1 for example:

              ToFromCurrent behavior
              DvInt1sbyteCopy the value as it is
              DvInt1sbyte?Assign missing value if null otherwise copy the value as it is
              sbyteDvInt1Copy if not a missing value otherwise throw exception
              sbyte?DvInt1Assign null for missing values otherwise copy over
              DvInt1DvBoolAssign missing value for a missing value otherwise copy value over
              DvInt1DvInt2Cast raw value from short to sbyte and compare it with original value if they are not same assign missing value otherwise casted value
              DvInt1DvInt4Same as above
              DvInt1DvInt8Same as above
              DvInt1Float
              DvInt1DoubleSame as above
              FloatDvInt1Assign NaN for missing value
              DoubleDvInt1Same as above

              Similar conversion rules exist for DvInt2, DvInt4, DvInt8 and DvBool.

              Logical, bitwise and numerical operators

              Operations such as ==, !=, !, >, >=, <, <=, +,-,*,pow,|,& take place
              between same DvTypes only. They also handle missing values and in the case of arithmetic operators
              overflow is also handled. Most of these overrides are implemented but only few are actively used.
              Whenever there is an overflow the resulting value is represented as missing value and the same goes
              when one of the operands is a missing value.

              Serialization

              DvTypes have their own codecs for efficiently compressing data and writing it to disk, for example,
              to write DvBool to disk, two bits are used to represent a boolean value, 0x00 is false, 0x01 is true
              and 0x10 is missing value indicator. Boolean values are written at the level of int32 which has 32
              bits that can accommodate 32/2 or 16 boolean values in 4 bytes as opposed to using 1 byte per
              boolean value using the naive approach that does not even handle missing value. We can reuse this
              approach to serialize bool by using one bit instead of two. DvInt* codecs need not be changed at
              all. DateTime and DvText codecs will require some changes.

              Intermediate Language(IL) code generation

              ML.NET contains a mini compiler that generates IL code at runtime for peak and poke functions that
              basically perform reflection of objects to set and get values in a more performant manner. Here we
              can use OpCodes.Stobj to emit IL code for DvTimeSpan,DvDateTime, DvDateTimeZone and
              ReadOnlyMemory<char> types.

              New Behavior

              • DvInt1, DvInt2, DvInt4, DvInt8 will be replaced with sbyte, short, int and long
                respectively.

                • Conversions will conform to .NET standard conversions.
                • Types will be converted using casting and this might cause underflow and overflow and therefore
                  behavior is undefined here, example, casting long to sbyte will result in assigning of low 8
                  bits from long to sbyte. ML.NET projects by default are unchecked because checked is expensive
                  and hence used in code blocks where it is needed.
                  > unchecked((sbyte)long.MaxValue)
                  -1
                  
                • Conversion from Text to Integer type is done by first converting Text to long value in
                  the case of positive number and ulong in the case of negative number and then validating this
                  value is within the legal bounds of the type that it is being converted to from Text type,
                  example, legal bound for sbyte is -128 to 127, so converting "-129" or "128" will result in an
                  exception, also converting a value that is out of legal bounds for a long type will also
                  result in an exception.
                  var c = Convert.ToSByte("129");
                  Value was either too large or too small for a signed byte.
                  sbyte.Parse(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
                  System.Convert.ToSByte(string)
                  
              • DvTimeSpan, DvDateTime and DvDateTimeZone will be replaced with TimeSpan, DateTime and
                DateTimeOffset respectively.

                • Offset in DataTimeOffset is represented as long because it records the ticks. Previously this
                  was represented as DvInt2 or short in DvDateTimeZone because it was recorded as minutes and
                  due to this it had a smaller footprint on the disk. With offset being long the footprint will
                  increase, one work around is to convert it to minutes before writing and then converting minutes
                  back to ticks but this might lead to loss in precision. Since DataTime is very rarely used in
                  Machine Learning so I'm not sure if it is worth making an optimization here.
              • DvText will be replaced with ReadOnlyMemory<char>.

                • ReadOnlyMemory<char> does not implement IEquatable<T> and due to this it cannot be be used a
                  type in GroupKeyColumnChecker in Cursor in GroupTransform. The workaround for this is to
                  remove the IEquatable<T> contraint on the type and instead use if else to check if the type
                  implements IEquatable<T> then cast and call Equals method otherwise check if the type is of
                  ReadOnlyMemory<char> then use its utility method for equality otherwise throw an exception.
                • ReadOnlyMemory<char> does not implement GetHashCode() and due to this it cannot be used as a
                  key in a dictionary in ReconcileSlotNames<T> in EvaluatorUtils.cs. The workaround for this is
                  to use string representation of ReadOnlyMemory<char> as a key. While this is wastage of memory
                  but its not too bad because this is only used at the end of evaluation phase and the number of
                  strings allocated here will be roughly proportional to the number of classes.
              • DvBool will be replaced with bool.

                • GetPredictedLabel and GetPredictedLabelCore will result in an undefined behavior in the case
                  where score contains a missing value represented as NaN. Here we will default to false.
              • Backward compatiblity when reading IDV files written with DvTypes.

                • Integers are read as they were written to disk, i.e minimum value of the corresponding data
                  type in the case of missing value.
                • Boolean is read using the old codec, where two bits are used per value and missing values are
                  converted to false to fit in bool type.
                • DateTime, DateTimeSpan, DateTimeZone use long and short type underneath to represent
                  ticks and offset and they are converted using the Integer scheme defined above. In the case
                  where ticks or offset is read and found to contain missing value represented as a minimum of
                  the underlying type then it is converted to default value of that type to prevent an exception
                  from DateTime or TimeSpan or DateTimeOffset class as such minimum values indicate an
                  invalid date.
                • DvText is read as it is. Missing values when being converted to Integer types are converted to
                  minimum value of that integer type and empty string is converted to default value of that
                  integer type.
              • TextLoader

                • Will throw an exception if it encounters missing value.
                • Will convert empty string to default values of type it is being converted to.
              • Parquet Loader

                • Will throw an exception for nullables or overflow.

              Future consideration

              Introduce an option in the loader whether to throw an exception in the case of missing value or just
              replace them with default values. With the current design we will throw an exception in the case
              of missing for Text Loader and Parquet loader but not IDV(Binary Loader).

              Benchmarking the type system changes

              (this section was written by @najeeb-kazmi )

              ReadOnlyMemory<char> is a data type introduced recently that allows management of strings without unnecessary memory allocation. Strings in C# are immutable. Hence, when we take a string operation such as substring, the resulting string is copied to a new memory location. To prevent unnecessary allocation of memory, ReadOnlyMemory keeps track of the substring via start and end offsets relative to the original string. Hence, for every substring operation, the memory allocated is constant. In ReadOnlyMemory, if one needs to access independent elements, they do it by calling the Span property, which returns a ReadOnlySpan object, which is a stack only concept. It turns out that this Span property is an expensive operation, and our initial benchmarks showed that runtimes of the pipelines regressed by 100%. Upon further performance analysis, we decide to cache the returned ReadOnlySpan as much as we could, and that brought the runtimes on par with DvText.

              These benchmarks are intended to compare performance after these optimizations on Span were done, in order to investigate whether we hit parity with DvText or not.

              Datasets and pipelines

              We chose datasets and pipelines to test to cover a variety of scenarios, including:

              • numeric data only
              • numeric + categorical data with categorical transform
              • numeric + categorical data with categorical and categorical hash transforms
              • categorical + text data with categorical and text transforms
              • text transform only on a very large text dataset

              The table below shows the datasets and their characteristics, as well as the pipeline that we executed on each dataset. All datasets were ingested in text format, which makes heavy use of DvText / ReadOnlyMemory<char>. Other data types are also involved in the pipelines, although the performance of the pipelines are dominated by DvText / ReadOnlyMemory<char>.

              DatasetSizeRowsFeaturesPipelineComments
              Criteo230 MB1M13 numeric 26 categoricalTrain data={\ct01\data\Criteo\Kaggle\train-1M.txt} loader=TextLoader{ col=Label:R4:0 col=NumFeatures:R4:1-13 col=LowCardCat:TX:19,22,30,33 col=HighCardCat:TX:~ } xf=CategoricalTransform{col=LowCardCat} xf=CategoricalHashTransform{col=HighCardCat bits=16} xf=MissingValueIndicatorTransform{col=NumFeatures} xf=Concat{ col=Features:NumFeatures,LowCardCat,HighCardCat } tr=ap{iter=10} seed=1 cache=-Numeric + categorical features with categorical and categorical hash transforms
              Bing Click Prediction3 GB500k3076 numericTrain data={\ct01\data\TeamOnly\NumericalDatasets\Ranking\BingClickPrediction\train-500K} loader=TextLoader{col=Label:R4:0 col=Features:R4:8-3083 header=+ quote=-} xf=NAHandleTransform{col=Features ind=-} tr=SDCA seed=1 cache=-Numeric features only
              Flight Delay227 MB7M5 numeric 3 categoricalTrain data={\ct01\data\PerformanceAnalysis\Data\Flight\New\FD2007train.csv} loader=TextLoader{ sep=, col=Month:R4:0 col=DayofMonth:R4:1 col=DayofWeek:R4:2 col=DepTime:R4:3 col=Distance:R4:4 col=UniqueCarrier:TX:5 col=Origin:TX:6 col=Dest:TX:7 col=Label:R4:9 header=+ } xf=CategoricalTransform{ col=UniqueCarrier col=Origin col=Dest } xf=Concat{ col=Features:Month,DayofMonth,DayofWeek,DepTime,Distance,UniqueCarrier,Origin,Dest } tr=SDCA seed=1 cache=-Numeric + categorical features with categorical transform
              Wikipedia Detox74 MB160k1 categorical 1 text columnTrain data={\ct01\data\SCRATCH_TO_MOVE\BinaryClassification\WikipediaDetox\toxicity_annotated_comments.merged.shuf-75MB,_160k-rows.tsv} loader=TextLoader{ quote=- sparse=- col=Label:R4:0 col=rev_id:TX:1 col=text:TX:2 col=year:TX:3 col=logged_in:BL:4 col=ns:TX:5 col=sample:TX:6 col=split:TX:7 header=+ } xf=Convert{col=logged_in type=R4} xf=CategoricalTransform{col=ns} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=FeaturesText:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } xf=Concat{col=Features:logged_in,ns,FeaturesText} tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Categorical transform + text featurization
              Amazon Reviews9 GB18M1 text columnTrain data={\ct01\users\prroy\dataset\cleandata_VW\Amazon_reviews_cleaned.tsv} loader=TextLoader{col=Label:TX:0 col=text:TX:1 header=+ sparse=-} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=Features:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Text featurization on a very large dataset

              Methodology and experimental setup

              • The two builds of ML.NET (one using DvTypes and the other using .NET data types) were built to target .NET Core 2.1.
              • Pipelines were executed from the Microsoft.ML.Console project: dotnet MML.dll <pipeline>
              • All pipelines were executed on Azure Standard F72s_v2 VMs running Windows Server 2016, which offer an instance isolated to dedicated hardware (Intel Xeon Platinum 8168).
              • We killed background processes that were not needed to run the experiments, including closing Visual Studio, ensuring that only one console window was open on the VM.
              • For each pipeline, we discarded the results of the first two runs for each pipeline to control for runtime variability due to a cold start, keeping only the subsequent runs for analysis.

              Results

              We present the results of the benchmarks here. The deltas indicate performance gap of .NET data types relative to DvTypes: negative values indicate slower performance of .NET data types compared to DvTypes, and percentage deltas are based off the mean runtime for DvTypes. Finally, we did an independent samples t-test with unequal variances for the two builds, and present the p-values for each test. We chose a significance threshold of 0.05, with a smaller p-value indicating significant differences.

              We can see that for all the pipelines except the one with Amazon Reviews dataset, the deltas were within 1% of the speed of DvTypes, and were not significant. For Amazon Reviews, the delta was 1.85% of the speed of DvTypes and significant. The statistical significance is not particularly concerning here because the long runtimes on this dataset were bound to return significantly different runtimes even with a small percentage difference. More important thing here is that the performance gap was reduced from ~100% to within 2%. We expect the performance to only improve with further optimizations in future .NET Core runtimes.

              Criteo 1M

              Run #.NET data typesDvTypes
              112.90712.634
              212.63512.847
              312.98912.546
              412.70812.713
              512.78912.463
              612.56512.751
              712.82812.73
              812.68812.425
              912.79113.009
              1012.85812.584
              Mean12.775812.6702
              S.D.0.1287208870.178014232
              Delta-0.1056-0.83%
              p-value0.073767344Not significant

              Flight Delay 7M

              Run #.NET data typesDvTypes
              152.53651.562
              252.66752.501
              352.17552.475
              452.07651.773
              554.1951.786
              651.67852.698
              752.64752.338
              852.42652.704
              951.70351.214
              1051.74252.407
              Mean52.38452.1458
              S.D.0.741520.520013632
              Delta-0.2382-0.46%
              p-value0.208863Not significant

              Bing Click Prediction 500K

              Run #.NET data typesDvTypes
              1222221
              2222222
              3220223
              4221223
              5220220
              6223219
              7222222
              8223220
              9223223
              10222222
              Mean221.8221.5
              S.D.1.1352921.433721
              Delta-0.3-0.14%
              p-value0.305291Not significant

              Wikipedia Detox

              Run #.NET data typesDvTypes
              165.99265.265
              266.04265.308
              365.667.457
              465.14666.011
              566.19665.788
              665.68367.611
              765.49865.191
              865.81966.636
              965.89665.412
              1066.56466.381
              1166.39266.074
              1265.86265.155
              1365.95864.808
              1466.08565.157
              1566.08566.116
              1666.11666.189
              1766.08665.748
              1866.82266.066
              1966.22765.009
              2065.27865.911
              Mean65.9673565.86465
              S.D.0.4026670.758248
              Delta-0.1027-0.16%
              p-value0.29838Not significant

              Amazon Reviews

              Run #.NET data typesDvTypes
              151214992
              251215016
              350905036
              451634981
              551125003
              650755008
              750975022
              850934991
              950715040
              1050905019
              Mean5103.35010.8
              S.D.27.1008419.46393
              Delta-92.5-1.85%
              p-value7.05E-08Significant

              CC: @eerhardt@Zruty0@Ivanidzo4ka@TomFinley@shauheen@najeeb-kazmi@markusweimer

              Metadata

              Metadata

              Assignees

              Labels

              APIIssues pertaining the friendly API

              Type

              No type

              Projects

              No projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                .NET data type system instead of DvTypes #673

                Description

                @codemzs

                .NET data type system instead of DvTypes

                Motivation

                Machine Learning datasets often have missing values and to accommodate them along with C# native
                types without increasing the memory footprint DvType system was created. If we were to use
                Nullable<T> then we are looking at additional memory for HasValue boolean field plus another 3
                bytes for 4 byte alignment. The C# native types that are replaced using DvTypes are bool as DvBool,
                sbyte as DvInt1, int16 as DvInt2, int32 as DvInt4, int64 as DvInt8, DvDateTime as System.DateTime,
                DvDateTimeZone as combination of DvDateTime and DvInt2 offset, DvTimeSpan as SysTimeSpan and string
                as DvText. Float and Double types already have a special value called NaN that can be used for
                missing value. DvType system achieves a smaller memory footprint by denoting special value for
                missing value which is usually the smallest number that can be represented by the native type that
                is encapsulated by DvType, example, DvInt1's missing value indicator would be SByte.MinValue and in
                the case of types that represent date/time types it is a value that represent maximum ticks.

                We plan to remove DvTypes to make IDataView a general commodity that can be used in other products
                and for this to happen it would be nice if it did not having a dependency on a special type system.
                If in future we find having DvTypes was useful then we can consider exposing it natively from .NET
                platform. Once we remove DvTypes then ML.NET platform will be using native non-nullable C# types.
                Float or double types can be used to represent missing value.

                Column Types

                Columns in ML.NET make up the dataset and ColumnType defines a column. At high level there are two
                kinds of column, first is PrimitiveType and that comprises of types such as NumberType,
                BoolType, TextType, DateTimeType, DateTimeZoneType, KeyType, second is Structured type
                and it comparises of VectorType. ColumnType is primarily made up of Type and DataKind.
                Type could refer to any type but it is instantiated with a type referred by DataKind which is an
                identifer for data types that comprises of DvTypes, native C# types such as float, double and custom
                big integer UInt128.

                Type conversion

                DvTypes have implicit and explicit override for assignment operator that handles type conversion.
                Lets consider DvInt1 for example:

                ToFromCurrent behavior
                DvInt1sbyteCopy the value as it is
                DvInt1sbyte?Assign missing value if null otherwise copy the value as it is
                sbyteDvInt1Copy if not a missing value otherwise throw exception
                sbyte?DvInt1Assign null for missing values otherwise copy over
                DvInt1DvBoolAssign missing value for a missing value otherwise copy value over
                DvInt1DvInt2Cast raw value from short to sbyte and compare it with original value if they are not same assign missing value otherwise casted value
                DvInt1DvInt4Same as above
                DvInt1DvInt8Same as above
                DvInt1Float
                DvInt1DoubleSame as above
                FloatDvInt1Assign NaN for missing value
                DoubleDvInt1Same as above

                Similar conversion rules exist for DvInt2, DvInt4, DvInt8 and DvBool.

                Logical, bitwise and numerical operators

                Operations such as ==, !=, !, >, >=, <, <=, +,-,*,pow,|,& take place
                between same DvTypes only. They also handle missing values and in the case of arithmetic operators
                overflow is also handled. Most of these overrides are implemented but only few are actively used.
                Whenever there is an overflow the resulting value is represented as missing value and the same goes
                when one of the operands is a missing value.

                Serialization

                DvTypes have their own codecs for efficiently compressing data and writing it to disk, for example,
                to write DvBool to disk, two bits are used to represent a boolean value, 0x00 is false, 0x01 is true
                and 0x10 is missing value indicator. Boolean values are written at the level of int32 which has 32
                bits that can accommodate 32/2 or 16 boolean values in 4 bytes as opposed to using 1 byte per
                boolean value using the naive approach that does not even handle missing value. We can reuse this
                approach to serialize bool by using one bit instead of two. DvInt* codecs need not be changed at
                all. DateTime and DvText codecs will require some changes.

                Intermediate Language(IL) code generation

                ML.NET contains a mini compiler that generates IL code at runtime for peak and poke functions that
                basically perform reflection of objects to set and get values in a more performant manner. Here we
                can use OpCodes.Stobj to emit IL code for DvTimeSpan,DvDateTime, DvDateTimeZone and
                ReadOnlyMemory<char> types.

                New Behavior

                • DvInt1, DvInt2, DvInt4, DvInt8 will be replaced with sbyte, short, int and long
                  respectively.

                  • Conversions will conform to .NET standard conversions.
                  • Types will be converted using casting and this might cause underflow and overflow and therefore
                    behavior is undefined here, example, casting long to sbyte will result in assigning of low 8
                    bits from long to sbyte. ML.NET projects by default are unchecked because checked is expensive
                    and hence used in code blocks where it is needed.
                    > unchecked((sbyte)long.MaxValue)
                    -1
                    
                  • Conversion from Text to Integer type is done by first converting Text to long value in
                    the case of positive number and ulong in the case of negative number and then validating this
                    value is within the legal bounds of the type that it is being converted to from Text type,
                    example, legal bound for sbyte is -128 to 127, so converting "-129" or "128" will result in an
                    exception, also converting a value that is out of legal bounds for a long type will also
                    result in an exception.
                    var c = Convert.ToSByte("129");
                    Value was either too large or too small for a signed byte.
                    sbyte.Parse(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
                    System.Convert.ToSByte(string)
                    
                • DvTimeSpan, DvDateTime and DvDateTimeZone will be replaced with TimeSpan, DateTime and
                  DateTimeOffset respectively.

                  • Offset in DataTimeOffset is represented as long because it records the ticks. Previously this
                    was represented as DvInt2 or short in DvDateTimeZone because it was recorded as minutes and
                    due to this it had a smaller footprint on the disk. With offset being long the footprint will
                    increase, one work around is to convert it to minutes before writing and then converting minutes
                    back to ticks but this might lead to loss in precision. Since DataTime is very rarely used in
                    Machine Learning so I'm not sure if it is worth making an optimization here.
                • DvText will be replaced with ReadOnlyMemory<char>.

                  • ReadOnlyMemory<char> does not implement IEquatable<T> and due to this it cannot be be used a
                    type in GroupKeyColumnChecker in Cursor in GroupTransform. The workaround for this is to
                    remove the IEquatable<T> contraint on the type and instead use if else to check if the type
                    implements IEquatable<T> then cast and call Equals method otherwise check if the type is of
                    ReadOnlyMemory<char> then use its utility method for equality otherwise throw an exception.
                  • ReadOnlyMemory<char> does not implement GetHashCode() and due to this it cannot be used as a
                    key in a dictionary in ReconcileSlotNames<T> in EvaluatorUtils.cs. The workaround for this is
                    to use string representation of ReadOnlyMemory<char> as a key. While this is wastage of memory
                    but its not too bad because this is only used at the end of evaluation phase and the number of
                    strings allocated here will be roughly proportional to the number of classes.
                • DvBool will be replaced with bool.

                  • GetPredictedLabel and GetPredictedLabelCore will result in an undefined behavior in the case
                    where score contains a missing value represented as NaN. Here we will default to false.
                • Backward compatiblity when reading IDV files written with DvTypes.

                  • Integers are read as they were written to disk, i.e minimum value of the corresponding data
                    type in the case of missing value.
                  • Boolean is read using the old codec, where two bits are used per value and missing values are
                    converted to false to fit in bool type.
                  • DateTime, DateTimeSpan, DateTimeZone use long and short type underneath to represent
                    ticks and offset and they are converted using the Integer scheme defined above. In the case
                    where ticks or offset is read and found to contain missing value represented as a minimum of
                    the underlying type then it is converted to default value of that type to prevent an exception
                    from DateTime or TimeSpan or DateTimeOffset class as such minimum values indicate an
                    invalid date.
                  • DvText is read as it is. Missing values when being converted to Integer types are converted to
                    minimum value of that integer type and empty string is converted to default value of that
                    integer type.
                • TextLoader

                  • Will throw an exception if it encounters missing value.
                  • Will convert empty string to default values of type it is being converted to.
                • Parquet Loader

                  • Will throw an exception for nullables or overflow.

                Future consideration

                Introduce an option in the loader whether to throw an exception in the case of missing value or just
                replace them with default values. With the current design we will throw an exception in the case
                of missing for Text Loader and Parquet loader but not IDV(Binary Loader).

                Benchmarking the type system changes

                (this section was written by @najeeb-kazmi )

                ReadOnlyMemory<char> is a data type introduced recently that allows management of strings without unnecessary memory allocation. Strings in C# are immutable. Hence, when we take a string operation such as substring, the resulting string is copied to a new memory location. To prevent unnecessary allocation of memory, ReadOnlyMemory keeps track of the substring via start and end offsets relative to the original string. Hence, for every substring operation, the memory allocated is constant. In ReadOnlyMemory, if one needs to access independent elements, they do it by calling the Span property, which returns a ReadOnlySpan object, which is a stack only concept. It turns out that this Span property is an expensive operation, and our initial benchmarks showed that runtimes of the pipelines regressed by 100%. Upon further performance analysis, we decide to cache the returned ReadOnlySpan as much as we could, and that brought the runtimes on par with DvText.

                These benchmarks are intended to compare performance after these optimizations on Span were done, in order to investigate whether we hit parity with DvText or not.

                Datasets and pipelines

                We chose datasets and pipelines to test to cover a variety of scenarios, including:

                • numeric data only
                • numeric + categorical data with categorical transform
                • numeric + categorical data with categorical and categorical hash transforms
                • categorical + text data with categorical and text transforms
                • text transform only on a very large text dataset

                The table below shows the datasets and their characteristics, as well as the pipeline that we executed on each dataset. All datasets were ingested in text format, which makes heavy use of DvText / ReadOnlyMemory<char>. Other data types are also involved in the pipelines, although the performance of the pipelines are dominated by DvText / ReadOnlyMemory<char>.

                DatasetSizeRowsFeaturesPipelineComments
                Criteo230 MB1M13 numeric 26 categoricalTrain data={\ct01\data\Criteo\Kaggle\train-1M.txt} loader=TextLoader{ col=Label:R4:0 col=NumFeatures:R4:1-13 col=LowCardCat:TX:19,22,30,33 col=HighCardCat:TX:~ } xf=CategoricalTransform{col=LowCardCat} xf=CategoricalHashTransform{col=HighCardCat bits=16} xf=MissingValueIndicatorTransform{col=NumFeatures} xf=Concat{ col=Features:NumFeatures,LowCardCat,HighCardCat } tr=ap{iter=10} seed=1 cache=-Numeric + categorical features with categorical and categorical hash transforms
                Bing Click Prediction3 GB500k3076 numericTrain data={\ct01\data\TeamOnly\NumericalDatasets\Ranking\BingClickPrediction\train-500K} loader=TextLoader{col=Label:R4:0 col=Features:R4:8-3083 header=+ quote=-} xf=NAHandleTransform{col=Features ind=-} tr=SDCA seed=1 cache=-Numeric features only
                Flight Delay227 MB7M5 numeric 3 categoricalTrain data={\ct01\data\PerformanceAnalysis\Data\Flight\New\FD2007train.csv} loader=TextLoader{ sep=, col=Month:R4:0 col=DayofMonth:R4:1 col=DayofWeek:R4:2 col=DepTime:R4:3 col=Distance:R4:4 col=UniqueCarrier:TX:5 col=Origin:TX:6 col=Dest:TX:7 col=Label:R4:9 header=+ } xf=CategoricalTransform{ col=UniqueCarrier col=Origin col=Dest } xf=Concat{ col=Features:Month,DayofMonth,DayofWeek,DepTime,Distance,UniqueCarrier,Origin,Dest } tr=SDCA seed=1 cache=-Numeric + categorical features with categorical transform
                Wikipedia Detox74 MB160k1 categorical 1 text columnTrain data={\ct01\data\SCRATCH_TO_MOVE\BinaryClassification\WikipediaDetox\toxicity_annotated_comments.merged.shuf-75MB,_160k-rows.tsv} loader=TextLoader{ quote=- sparse=- col=Label:R4:0 col=rev_id:TX:1 col=text:TX:2 col=year:TX:3 col=logged_in:BL:4 col=ns:TX:5 col=sample:TX:6 col=split:TX:7 header=+ } xf=Convert{col=logged_in type=R4} xf=CategoricalTransform{col=ns} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=FeaturesText:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } xf=Concat{col=Features:logged_in,ns,FeaturesText} tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Categorical transform + text featurization
                Amazon Reviews9 GB18M1 text columnTrain data={\ct01\users\prroy\dataset\cleandata_VW\Amazon_reviews_cleaned.tsv} loader=TextLoader{col=Label:TX:0 col=text:TX:1 header=+ sparse=-} xf=NAFilter{col=Label} xf=Term{col=Label:Label} xf=TextTransform{ col=Features:text wordExtractor=NgramExtractorTransform{ngram=2} charExtractor=NgramExtractorTransform{ngram=3} } tr=OVA {p=AveragedPerceptron{iter=10}} seed=1 cache=-Text featurization on a very large dataset

                Methodology and experimental setup

                • The two builds of ML.NET (one using DvTypes and the other using .NET data types) were built to target .NET Core 2.1.
                • Pipelines were executed from the Microsoft.ML.Console project: dotnet MML.dll <pipeline>
                • All pipelines were executed on Azure Standard F72s_v2 VMs running Windows Server 2016, which offer an instance isolated to dedicated hardware (Intel Xeon Platinum 8168).
                • We killed background processes that were not needed to run the experiments, including closing Visual Studio, ensuring that only one console window was open on the VM.
                • For each pipeline, we discarded the results of the first two runs for each pipeline to control for runtime variability due to a cold start, keeping only the subsequent runs for analysis.

                Results

                We present the results of the benchmarks here. The deltas indicate performance gap of .NET data types relative to DvTypes: negative values indicate slower performance of .NET data types compared to DvTypes, and percentage deltas are based off the mean runtime for DvTypes. Finally, we did an independent samples t-test with unequal variances for the two builds, and present the p-values for each test. We chose a significance threshold of 0.05, with a smaller p-value indicating significant differences.

                We can see that for all the pipelines except the one with Amazon Reviews dataset, the deltas were within 1% of the speed of DvTypes, and were not significant. For Amazon Reviews, the delta was 1.85% of the speed of DvTypes and significant. The statistical significance is not particularly concerning here because the long runtimes on this dataset were bound to return significantly different runtimes even with a small percentage difference. More important thing here is that the performance gap was reduced from ~100% to within 2%. We expect the performance to only improve with further optimizations in future .NET Core runtimes.

                Criteo 1M

                Run #.NET data typesDvTypes
                112.90712.634
                212.63512.847
                312.98912.546
                412.70812.713
                512.78912.463
                612.56512.751
                712.82812.73
                812.68812.425
                912.79113.009
                1012.85812.584
                Mean12.775812.6702
                S.D.0.1287208870.178014232
                Delta-0.1056-0.83%
                p-value0.073767344Not significant

                Flight Delay 7M

                Run #.NET data typesDvTypes
                152.53651.562
                252.66752.501
                352.17552.475
                452.07651.773
                554.1951.786
                651.67852.698
                752.64752.338
                852.42652.704
                951.70351.214
                1051.74252.407
                Mean52.38452.1458
                S.D.0.741520.520013632
                Delta-0.2382-0.46%
                p-value0.208863Not significant

                Bing Click Prediction 500K

                Run #.NET data typesDvTypes
                1222221
                2222222
                3220223
                4221223
                5220220
                6223219
                7222222
                8223220
                9223223
                10222222
                Mean221.8221.5
                S.D.1.1352921.433721
                Delta-0.3-0.14%
                p-value0.305291Not significant

                Wikipedia Detox

                Run #.NET data typesDvTypes
                165.99265.265
                266.04265.308
                365.667.457
                465.14666.011
                566.19665.788
                665.68367.611
                765.49865.191
                865.81966.636
                965.89665.412
                1066.56466.381
                1166.39266.074
                1265.86265.155
                1365.95864.808
                1466.08565.157
                1566.08566.116
                1666.11666.189
                1766.08665.748
                1866.82266.066
                1966.22765.009
                2065.27865.911
                Mean65.9673565.86465
                S.D.0.4026670.758248
                Delta-0.1027-0.16%
                p-value0.29838Not significant

                Amazon Reviews

                Run #.NET data typesDvTypes
                151214992
                251215016
                350905036
                451634981
                551125003
                650755008
                750975022
                850934991
                950715040
                1050905019
                Mean5103.35010.8
                S.D.27.1008419.46393
                Delta-92.5-1.85%
                p-value7.05E-08Significant

                CC: @eerhardt@Zruty0@Ivanidzo4ka@TomFinley@shauheen@najeeb-kazmi@markusweimer

                Metadata

                Metadata

                Assignees

                Labels

                APIIssues pertaining the friendly API

                Type

                No type

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions