Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Added IDisposable support for several classes #4939

Merged
merged 13 commits into from
Mar 24, 2020
Merged

Added IDisposable support for several classes #4939

merged 13 commits into from
Mar 24, 2020

Conversation

harishsk
Copy link
Contributor

@harishsk harishsk commented Mar 12, 2020

We have had a lot of instability in the Tensorflow tests. At least one of the issues has to do with memory leaks from ImageClassificationTrainer which holds references to the Tensorflow session and graphs. In order to fix this I have added IDisposable support for several of the classes involved in this scenario and fixed up the tests to call Dispose at the end of the tests.

@harishsk

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@harishsk

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@harishsk

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@harishsk

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@harishsk

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@harishsk

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@harishsk harishsk marked this pull request as ready for review March 22, 2020 19:36
@harishsk harishsk requested a review from a team as a code owner March 22, 2020 19:36
@harishsk harishsk changed the title Draft PR to debug tensorflow issues Added IDisposable support for several classes Mar 22, 2020
#region IDisposable Support
private bool _disposed;

private void Dispose(bool disposing)
Copy link
Member

@sharwell sharwell Mar 22, 2020

Choose a reason for hiding this comment

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

📝 This is the legacy IDisposable pattern. The new way to implement this is moving this implementation code directly into IDisposable.Dispose(). #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Weren't there two cases to be handled before? The one that calls Dispose(true) from from IDisposable.Dispose() and the other that calls Dispose(false) from the finalizer? (Or someplace else...I am not quite sure). How would that be handled?


In reply to: 396132625 [](ancestors = 396132625)

Copy link
Member

@sharwell sharwell Mar 22, 2020

Choose a reason for hiding this comment

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

the other that calls Dispose(false) from the finalizer

Finalizers are not allowed under the new dispose pattern. Since there are no finalizers, there are no calls to Dispose(false). Since there are no calls to dispose false, there is no need to add complexity by defining the method. #Resolved

@@ -15,7 +16,7 @@ namespace Microsoft.ML.Data
/// This class represents a data loader that applies a transformer chain after loading.
/// It also has methods to save itself to a repository.
/// </summary>
public sealed class CompositeDataLoader<TSource, TLastTransformer> : IDataLoader<TSource>
public sealed class CompositeDataLoader<TSource, TLastTransformer> : IDataLoader<TSource>, IDisposable
Copy link
Member

@sharwell sharwell Mar 22, 2020

Choose a reason for hiding this comment

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

📝 By making the class disposable when IDataLoader<TSource> is not disposable, it will be easy for a consumer to fail to dispose of the object. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't change interfaces now that they are shipped. Right now, when customers discover they have a memory leak, we don't have a solution. With this method at least we can tell them to add a Dispose call.

Is there a better approach we can consider?


In reply to: 396132696 [](ancestors = 396132696)

Copy link
Member

@sharwell sharwell Mar 22, 2020

Choose a reason for hiding this comment

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

Every case that I reviewed locally required breaking API changes. Default interface methods might be an option for .NET Core targets, but those are completely unsupported on .NET Framework. #Resolved

Copy link
Contributor

@justinormont justinormont Mar 23, 2020

Choose a reason for hiding this comment

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

Is the component GA? Or can we still make a breaking change? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All public interfaces are frozen. This would be one of those.


In reply to: 396753025 [](ancestors = 396753025)

Copy link
Contributor

Choose a reason for hiding this comment

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

All public interfaces are frozen. This would be one of those.

There may be a difference between interfaces released as GA and the preview releases. I'm not sure which one this falls under.

/cc @eerhardt

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IDataLoader is a core interface. It is part of the GA set.


In reply to: 396760924 [](ancestors = 396760924)

@azure-pipelines

This comment has been minimized.

1 similar comment
@azure-pipelines

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@azure-pipelines

This comment has been minimized.

@@ -463,6 +471,7 @@ public void TensorFlowTransformInputOutputTypesTest()
}
Assert.False(cursor.MoveNext());
}
(tfModel as IDisposable).Dispose();
Copy link
Contributor

Choose a reason for hiding this comment

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

(tfModel as IDisposable).Dispose(); [](start = 12, length = 35)

(tfModel as IDisposable)?.Dispose();

public void TensorFlowImageClassification(ImageClassificationTrainer.Architecture arch)
{
if (NotCentOS7FactAttribute.IsCentOS7())
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

@frank-dong-ms-zz frank-dong-ms-zz left a comment

Choose a reason for hiding this comment

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

:shipit:

@harishsk harishsk requested a review from a team as a code owner March 23, 2020 05:48
@@ -38,13 +39,15 @@ public CompositeDataLoader(IDataLoader<TSource> loader, TransformerChain<TLastTr

Loader = loader;
Transformer = transformerChain ?? new TransformerChain<TLastTransformer>();
_disposed = false;
Copy link
Member

Choose a reason for hiding this comment

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

bools are initialized to false by the runtime. There is no need to re-initialize them to false here.

_disposed = true;
}

void IDisposable.Dispose()
Copy link
Member

Choose a reason for hiding this comment

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

Why are you explicitly implementing IDisposable? Instead, you can just have a public void Dispose() method here.

The reason to not explicitly implement an interface is so users of the class can just call Dispose() as necessary. They don't need to cast to IDisposable to call it.

if (_disposed)
return;

(_bindable as IDisposable)?.Dispose();
Copy link
Member

Choose a reason for hiding this comment

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

ISchemaBindableMapper is internal, so if you want you can make it inherit from IDisposable without a breaking change.

@@ -154,6 +154,8 @@ public void TensorFlowTransforCifarEndToEndTest2()
Assert.Equal(0, prediction.PredictedScores[0], 2);
Assert.Equal(0, prediction.PredictedScores[1], 2);
Assert.Equal(1, prediction.PredictedScores[2], 2);

transformer.Dispose();
Copy link
Member

Choose a reason for hiding this comment

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

You can use the using statement in all these tests. It will make this cleaner.

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement

@@ -389,7 +385,7 @@ public void TensorFlowTransformInputOutputTypesTest()

var inputs = new string[] { "f64", "f32", "i64", "i32", "i16", "i8", "u64", "u32", "u16", "u8", "b" };
var outputs = new string[] { "o_f64", "o_f32", "o_i64", "o_i32", "o_i16", "o_i8", "o_u64", "o_u32", "o_u16", "o_u8", "o_b" };
var tfModel = mlContext.Model.LoadTensorFlowModel(modelLocation);
using var tfModel = mlContext.Model.LoadTensorFlowModel(modelLocation);
Copy link
Contributor

Choose a reason for hiding this comment

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

Consistency/user-expectations:
How will a user know which model types and prediction engines are disposable? Should we convert all models and prediction engines to be disposable?

Seems it should be an all-or-none for consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that it should be all or none for consistency. If we go for none, then we need to make the interfaces inherit from IDisposable and we can't do that now without a breaking change. If for "all" then the Dispose() calls for most would become no-ops and only Tensorflow (right now) needs a real Dispose() call.
This seems like one of those cases that we will need to fix whenever we do a v2 release and can take breaking changes. Until then, I am okay with documenting for the tensorflow related transformers.


In reply to: 396761653 [](ancestors = 396761653)

@@ -1185,10 +1189,10 @@ public void TensorFlowSentimentClassificationTest()
// For explanation on how was the `sentiment_model` created
// c.f. https://github.com/dotnet/machinelearning-testdata/blob/master/Microsoft.ML.TensorFlow.TestModels/sentiment_model/README.md
string modelLocation = @"sentiment_model";
var pipelineModel = mlContext.Model.LoadTensorFlowModel(modelLocation).ScoreTensorFlowModel(new[] { "Prediction/Softmax" }, new[] { "Features" })
using var pipelineModel = mlContext.Model.LoadTensorFlowModel(modelLocation).ScoreTensorFlowModel(new[] { "Prediction/Softmax" }, new[] { "Features" })
Copy link
Contributor

@justinormont justinormont Mar 23, 2020

Choose a reason for hiding this comment

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

Similar changes may need to be made in the AutoML code, its use in Model Builder, and in CodeGen. Otherwise it will continue to leak in those components.

Todo: In a follow-up PR -- if needed add a using or call dispose() for TF models and TF prediction engines.

/cc @LittleLittleCloud

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have updated the docs and samples. I could not figure out easily where the corresponding fixes need to be bade in AutoML, Model Builder and CodeGen. @LittleLittleCloud, can you please help with that?


In reply to: 396764702 [](ancestors = 396764702)

Copy link
Contributor

@justinormont justinormont left a comment

Choose a reason for hiding this comment

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

LGTM from the AutoML side.

May want to file an issue for follow up work on the AutoML API & CodeGen fronts in this repo, an issue in the dotnet/machinelearning-samples repo for follow up work on the TF and the AutoML API samples; and another in the dotnet/machinelearning-modelbuilder.

(update) Should create an issue for updating docs to educate users when they have to dispose and when they do not.

@harishsk harishsk merged commit 5d531d3 into dotnet:master Mar 24, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Mar 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants