Skip to content

Commit 1a3b2c8

Browse files
Merge pull request #1389 from mvphelps/fix1387
Resolves #1387 and backfill tests for ThreadDisposeScopeStatistics
2 parents b30a60d + d75341a commit 1a3b2c8

9 files changed

+430
-113
lines changed

RELEASENOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ __Bug Fixes__:
1111

1212
#1383 `torch.linalg.vector_norm`: Make `ord`-argument optional, as specified in docs<br/>
1313
#1385 PackedSequence now participates in the DisposeScope system at the same level as Tensor objects.<br/>
14+
#1387 Attaching tensor to a DisposeScope no longer makes Statistics.DetachedFromScopeCount go negative.<br/>
1415

1516
# NuGet Version 0.103.0
1617

src/TorchSharp/DisposeScope.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -237,17 +237,6 @@ public IReadOnlyList<IDisposable> Attach(IEnumerable<IDisposable> disposables)
237237

238238
var result = new List<IDisposable>();
239239
foreach (var disposable in disposables) {
240-
if (disposable is torch.Tensor tensor) {
241-
if (tensor.OwningDisposeScope == null && !tensor.IsInvalid) {
242-
_disposeScopeManager.StatisticsInstance.DetachedFromScopeCount--;
243-
}
244-
}
245-
else if (disposable is torch.nn.utils.rnn.PackedSequence sequence) {
246-
if (sequence.OwningDisposeScope == null && !sequence.IsInvalid) {
247-
_disposeScopeManager.StatisticsInstance.DetachedFromScopeCount--;
248-
}
249-
}
250-
251240
AddToOther(this, disposable);
252241
result.Add(disposable);
253242
}

src/TorchSharp/ThreadDisposeScopeStatistics.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class ThreadDisposeScopeStatistics
2727
public long DetachedFromScopeCount { get; internal set; }
2828

2929
/// <summary>
30-
/// The number of disposables that are currently live on the current thread. If a It's aproximate, see
30+
/// The number of disposables that are currently live on the current thread. It's approximate, see
3131
/// Tensor.TotalCount. Disposables that weren't created within a DisposeScope, or detached from the dispose
3232
/// scope, will not be counted as alive.
3333
/// </summary>
Lines changed: 62 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,82 @@
11
using System.Reflection;
2-
using TorchSharp;
32
using Xunit;
43

5-
namespace TorchSharpTest;
6-
7-
public class TestDisposeScopesPackedSequence
4+
namespace TorchSharp
85
{
9-
[Fact]
10-
public void MoveDisposeScope()
6+
[Collection("Sequential")]
7+
public class TestDisposeScopesPackedSequence
118
{
12-
var sequences = CreateTestSequences();
13-
torch.nn.utils.rnn.PackedSequence packed_sequence;
14-
var otherScope = torch.NewDisposeScope();
15-
using (torch.NewDisposeScope())
9+
[Fact]
10+
public void MoveDisposeScope()
1611
{
17-
using (torch.NewDisposeScope())
18-
{
19-
packed_sequence = torch.nn.utils.rnn.pack_sequence(sequences, enforce_sorted: false);
12+
var sequences = CreateTestSequences();
13+
torch.nn.utils.rnn.PackedSequence packed_sequence;
14+
var otherScope = torch.NewDisposeScope();
15+
using (torch.NewDisposeScope()) {
16+
using (torch.NewDisposeScope()) {
17+
packed_sequence = torch.nn.utils.rnn.pack_sequence(sequences, enforce_sorted: false);
18+
AssertPackedSequenceValid(packed_sequence);
19+
20+
packed_sequence.MoveToOuterDisposeScope();
21+
}
22+
2023
AssertPackedSequenceValid(packed_sequence);
2124

22-
packed_sequence.MoveToOuterDisposeScope();
25+
packed_sequence.MoveToOtherDisposeScope(otherScope);
2326
}
27+
2428
AssertPackedSequenceValid(packed_sequence);
29+
otherScope.Dispose();
2530

26-
packed_sequence.MoveToOtherDisposeScope(otherScope);
31+
Assert.True(GetPackedSequenceIsInvalid(packed_sequence));
32+
Assert.True(packed_sequence.data.IsInvalid);
2733
}
2834

29-
AssertPackedSequenceValid(packed_sequence);
30-
otherScope.Dispose();
31-
32-
Assert.True(GetPackedSequenceIsInvalid(packed_sequence));
33-
Assert.True(packed_sequence.data.IsInvalid);
34-
}
35-
36-
[Fact]
37-
public void DisposablesValidityWhenNotSorted()
38-
{
39-
var sequences = CreateTestSequences();
40-
using var scope = torch.NewDisposeScope();
41-
var packed = torch.nn.utils.rnn.pack_sequence(sequences, enforce_sorted: false);
42-
Assert.Equal(1, scope.DisposablesCount);
43-
AssertPackedSequenceValid(packed);
44-
}
45-
46-
[Fact]
47-
public void DisposablesValidityWhenSorted()
48-
{
49-
var sequences = CreateTestSequences();
50-
using var scope = torch.NewDisposeScope();
51-
var packed = torch.nn.utils.rnn.pack_sequence(sequences, enforce_sorted: true);
52-
Assert.Equal(1, scope.DisposablesCount);
53-
Assert.False(GetPackedSequenceIsInvalid(packed));
54-
Assert.False(packed.batch_sizes.IsInvalid);
55-
Assert.False(packed.data.IsInvalid);
56-
Assert.True(packed.sorted_indices.IsInvalid);
57-
Assert.True(packed.unsorted_indices.IsInvalid);
58-
}
59-
60-
[Fact]
61-
public void DisposeScopeStatistics()
62-
{
63-
DisposeScopeManager.Statistics.Reset();
64-
AssertStatCounts(0, 0, 0, 0, 0);
65-
var sequences = CreateTestSequences();
66-
AssertStatCounts(0, 2, 0, 0, 0);
67-
var outOfScope = torch.nn.utils.rnn.pack_sequence(sequences, enforce_sorted: true);
68-
AssertStatCounts(0, 7, 0, 0, 0);
69-
using var scope = torch.NewDisposeScope();
70-
AssertStatCounts(0, 7, 0, 0, 0);
71-
72-
var inScope = torch.nn.utils.rnn.pack_sequence(sequences, enforce_sorted: true);
73-
AssertStatCounts(5, 7, 4, 0, 1);
74-
75-
scope.Attach(outOfScope);
76-
//Possible subtle bug. When attaching an object that isn't owned by any scope, the count subtracts.
77-
AssertStatCounts( 5, 7, 3, 0, 2);
78-
79-
scope.Detach(inScope);
80-
AssertStatCounts( 5, 7, 4, 0, 1);
81-
82-
outOfScope.Dispose();
83-
AssertStatCounts( 5, 7, 4, 5, -4);
84-
85-
}
35+
[Fact]
36+
public void DisposablesValidityWhenNotSorted()
37+
{
38+
var sequences = CreateTestSequences();
39+
using var scope = torch.NewDisposeScope();
40+
var packed = torch.nn.utils.rnn.pack_sequence(sequences, enforce_sorted: false);
41+
Assert.Equal(1, scope.DisposablesCount);
42+
AssertPackedSequenceValid(packed);
43+
}
8644

87-
private static void AssertStatCounts(long createdInScope, long createdOutsideScope, long detachedFrom, long disposedIn, long threadTotalLive)
88-
{
89-
var stats = DisposeScopeManager.Statistics;
90-
Assert.Equal(createdInScope, stats.CreatedInScopeCount);
91-
Assert.Equal(createdOutsideScope, stats.CreatedOutsideScopeCount);
92-
Assert.Equal(detachedFrom, stats.DetachedFromScopeCount);
93-
Assert.Equal(disposedIn, stats.DisposedInScopeCount);
94-
Assert.Equal(threadTotalLive, stats.ThreadTotalLiveCount);
95-
}
45+
[Fact]
46+
public void DisposablesValidityWhenSorted()
47+
{
48+
var sequences = CreateTestSequences();
49+
using var scope = torch.NewDisposeScope();
50+
var packed = torch.nn.utils.rnn.pack_sequence(sequences, enforce_sorted: true);
51+
Assert.Equal(1, scope.DisposablesCount);
52+
Assert.False(GetPackedSequenceIsInvalid(packed));
53+
Assert.False(packed.batch_sizes.IsInvalid);
54+
Assert.False(packed.data.IsInvalid);
55+
Assert.True(packed.sorted_indices.IsInvalid);
56+
Assert.True(packed.unsorted_indices.IsInvalid);
57+
}
9658

97-
private static torch.Tensor[] CreateTestSequences()
98-
{
99-
return new[]
59+
private static torch.Tensor[] CreateTestSequences()
10060
{
101-
torch.tensor(new long[] { 1, 2, 3, 4 }),
102-
torch.tensor(new long[] { 5, 6 }),
103-
};
104-
}
61+
return new[] { torch.tensor(new long[] { 1, 2, 3, 4 }), torch.tensor(new long[] { 5, 6 }), };
62+
}
10563

106-
private static void AssertPackedSequenceValid(torch.nn.utils.rnn.PackedSequence packed_sequence)
107-
{
108-
Assert.False(GetPackedSequenceIsInvalid(packed_sequence));
109-
Assert.False(packed_sequence.batch_sizes.IsInvalid);
110-
Assert.False(packed_sequence.data.IsInvalid);
111-
Assert.False(packed_sequence.sorted_indices.IsInvalid);
112-
Assert.False(packed_sequence.unsorted_indices.IsInvalid);
113-
}
64+
private static void AssertPackedSequenceValid(torch.nn.utils.rnn.PackedSequence packed_sequence)
65+
{
66+
Assert.False(GetPackedSequenceIsInvalid(packed_sequence));
67+
Assert.False(packed_sequence.batch_sizes.IsInvalid);
68+
Assert.False(packed_sequence.data.IsInvalid);
69+
Assert.False(packed_sequence.sorted_indices.IsInvalid);
70+
Assert.False(packed_sequence.unsorted_indices.IsInvalid);
71+
}
11472

115-
private static bool GetPackedSequenceIsInvalid(torch.nn.utils.rnn.PackedSequence packed_sequence)
116-
{
117-
//HACK: reflection to avoid exposing internal method IsInvalid in API.
118-
var getter = typeof(torch.nn.utils.rnn.PackedSequence).GetProperty("IsInvalid", BindingFlags.Instance | BindingFlags.NonPublic)!;
119-
return (bool)getter.GetValue(packed_sequence)!;
73+
private static bool GetPackedSequenceIsInvalid(torch.nn.utils.rnn.PackedSequence packed_sequence)
74+
{
75+
//HACK: reflection to avoid exposing internal method IsInvalid in API.
76+
var getter =
77+
typeof(torch.nn.utils.rnn.PackedSequence).GetProperty("IsInvalid",
78+
BindingFlags.Instance | BindingFlags.NonPublic)!;
79+
return (bool)getter.GetValue(packed_sequence)!;
80+
}
12081
}
12182
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Xunit;
2+
3+
namespace TorchSharp
4+
{
5+
public class TestDisposeScopesStatisticsBase
6+
{
7+
protected static void ResetStats()
8+
{
9+
DisposeScopeManager.Statistics.Reset();
10+
AssertStatCounts(0, 0, 0, 0, 0);
11+
}
12+
13+
protected static void AssertStatCounts(long createdInScope, long createdOutsideScope, long detachedFrom, long disposedIn, long threadTotalLive)
14+
{
15+
var stats = DisposeScopeManager.Statistics;
16+
Assert.True(createdInScope == stats.CreatedInScopeCount, $"CreatedInScope: Expected({createdInScope})!={stats.CreatedInScopeCount}");
17+
Assert.True(createdOutsideScope == stats.CreatedOutsideScopeCount, $"CreatedOutsideScopeCount: Expected({createdOutsideScope})!={stats.CreatedOutsideScopeCount}");
18+
Assert.True(detachedFrom == stats.DetachedFromScopeCount, $"DetachedFromScopeCount: Expected({detachedFrom})!={stats.DetachedFromScopeCount}");
19+
Assert.True(disposedIn == stats.DisposedInScopeCount, $"DisposedInScopeCount: Expected({disposedIn})!={stats.DisposedInScopeCount}");
20+
Assert.True(threadTotalLive == stats.ThreadTotalLiveCount, $"ThreadTotalLiveCount: Expected({threadTotalLive})!={stats.ThreadTotalLiveCount}");
21+
}
22+
}
23+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using TorchSharp;
2+
using Xunit;
3+
4+
namespace TorchSharp
5+
{
6+
[Collection("Sequential")]
7+
public class TestDisposeScopesStatisticsPackedSequenceScoped : TestDisposeScopesStatisticsBase
8+
{
9+
10+
//When reviewing these tests, refer first to TestDisposeScopesStatisticsTensor. The tests here are
11+
//essentially identical, except that the numbers all look weird because a PackedSequence is constructed
12+
//from Tensors, and contains Tensors that it detaches from any scope and manages itself.
13+
14+
15+
[Fact]
16+
public void CreatingIncrementsCreatedInScope()
17+
{
18+
using var scope = torch.NewDisposeScope();
19+
ResetStats();
20+
var ps = TestDisposeScopesStatisticsPackedSequenceUnscoped.Create();
21+
//CreatedInScope = 2 tensors (source data) + 4 internal PackedSequence tensors, + the PackedSequence
22+
//DetachedFromScope = the 4 internal PackedSequenceTensors
23+
AssertStatCounts(7, 0, 4, 0, 3);
24+
}
25+
26+
[Fact]
27+
public void AttachingIncrementsNothing()
28+
{
29+
using var scope = torch.NewDisposeScope();
30+
var ps = TestDisposeScopesStatisticsPackedSequenceUnscoped.Create();
31+
ResetStats();
32+
scope.Attach(ps);
33+
AssertStatCounts(0, 0, 0, 0, 0);
34+
}
35+
36+
[Fact]
37+
public void DetachingIncrementsDetached()
38+
{
39+
using var scope = torch.NewDisposeScope();
40+
var ps = TestDisposeScopesStatisticsPackedSequenceUnscoped.Create();
41+
ResetStats();
42+
scope.Detach(ps);
43+
AssertStatCounts(0, 0, 1, 0, -1);
44+
}
45+
46+
[Fact]
47+
public void DisposingIncrementsDisposed()
48+
{
49+
using var scope = torch.NewDisposeScope();
50+
var ps = TestDisposeScopesStatisticsPackedSequenceUnscoped.Create();
51+
ResetStats();
52+
ps.Dispose();
53+
//DisposedInScope = The PackedSequence + the 4 internal tensors
54+
AssertStatCounts(0, 0, 0, 5, -5);
55+
}
56+
57+
[Fact]
58+
public void DisposingScopeIncrementsDisposed()
59+
{
60+
var scope = torch.NewDisposeScope();
61+
var ps = TestDisposeScopesStatisticsPackedSequenceUnscoped.Create();
62+
ResetStats();
63+
scope.Dispose();
64+
//DisposedInScope = The PackedSequence and the 2 source data tensors. The internal tensors and not on the scope.
65+
AssertStatCounts(0, 0, 0, 3, -3);
66+
}
67+
68+
[Fact]
69+
public void DisposingScopeAfterDetachingCountsOnlyTheSourceDataTensors()
70+
{
71+
var scope = torch.NewDisposeScope();
72+
var ps = TestDisposeScopesStatisticsPackedSequenceUnscoped.Create();
73+
scope.Detach(ps);
74+
ResetStats();
75+
scope.Dispose();
76+
//DisposedInScope just the two source data tensors.
77+
AssertStatCounts(0, 0, 0, 2, -2);
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)