Skip to content

Comments

Remove vnt_size and size checks when setting in VNT#1281

Merged
penelopeysm merged 1 commit intobreakingfrom
py/no-vnt-size
Feb 21, 2026
Merged

Remove vnt_size and size checks when setting in VNT#1281
penelopeysm merged 1 commit intobreakingfrom
py/no-vnt-size

Conversation

@penelopeysm
Copy link
Member

@penelopeysm penelopeysm commented Feb 21, 2026

This PR removes size checks when setting a value inside a VNT. That means, for example, you can assign Dirichlet(ones(2)) (which has size (2,)) to x[1:5] even though the indices are not the same size:

julia> using DynamicPPL, Distributions

julia> vnt = @vnt begin
           @template x = zeros(5)
           x[1:5] := Dirichlet(ones(2))
       end
VarNamedTuple
└─ x => PartialArray size=(5,) data::Vector{DynamicPPL.VarNamedTuples.ArrayLikeBlock{Dirichlet{Float64, Vector{Float64}, Float64}, Tuple{UnitRange{Int64}}, @NamedTuple{}, Tuple{Int64}}}
        ├─ (1,) => DynamicPPL.VarNamedTuples.ArrayLikeBlock{Dirichlet{Float64, Vector{Float64}, Float64}, Tuple{UnitRange{Int64}}, @NamedTuple{}, Tuple{Int64}}(Dirichlet{Float64, Vector{Float64}, Float64}(alpha=[1.0, 1.0]), (1:5,), NamedTuple(), (5,))
        ├─ (2,) => DynamicPPL.VarNamedTuples.ArrayLikeBlock{Dirichlet{Float64, Vector{Float64}, Float64}, Tuple{UnitRange{Int64}}, @NamedTuple{}, Tuple{Int64}}(Dirichlet{Float64, Vector{Float64}, Float64}(alpha=[1.0, 1.0]), (1:5,), NamedTuple(), (5,))
        ├─ (3,) => DynamicPPL.VarNamedTuples.ArrayLikeBlock{Dirichlet{Float64, Vector{Float64}, Float64}, Tuple{UnitRange{Int64}}, @NamedTuple{}, Tuple{Int64}}(Dirichlet{Float64, Vector{Float64}, Float64}(alpha=[1.0, 1.0]), (1:5,), NamedTuple(), (5,))
        ├─ (4,) => DynamicPPL.VarNamedTuples.ArrayLikeBlock{Dirichlet{Float64, Vector{Float64}, Float64}, Tuple{UnitRange{Int64}}, @NamedTuple{}, Tuple{Int64}}(Dirichlet{Float64, Vector{Float64}, Float64}(alpha=[1.0, 1.0]), (1:5,), NamedTuple(), (5,))
        └─ (5,) => DynamicPPL.VarNamedTuples.ArrayLikeBlock{Dirichlet{Float64, Vector{Float64}, Float64}, Tuple{UnitRange{Int64}}, @NamedTuple{}, Tuple{Int64}}(Dirichlet{Float64, Vector{Float64}, Float64}(alpha=[1.0, 1.0]), (1:5,), NamedTuple(), (5,))

The reason for this is twofold.

Pointwise log-probabilities

One is to do with #1279. The issue there is that when accumulating pointwise log-densities in VNT, you can have a model that looks like

@model function ...
   x[1:2] ~ Dirichlet(ones(2))
end

It's only possible to associate a single float probability with the VarName x[1:2]. If there were size checks, it would be impossible to associate a true float with x[1:2], because the size of a float is () which doesn't line up with the expected size (2,). You would have to wrap a float in a struct like

struct SizedLogProb{T}
    lp::Float64
    sz::T
end
vnt_size(s::SizedLogProb) = s.sz

before storing it in a VNT. The problem with this is that when the user expects to get a log-probability, they'll now get this ugly SizedLogProb thing, which forces them to carry the burden of internal VNT details.

Sizes are checked at model runtime anyway

The main reason why we want to avoid storing something of the wrong size is because we want to avoid the possibility of constructing "inconsistent VNTs". Here I use "inconsistent" to mean VNTs precisely like the one above:

julia> vnt = @vnt begin
           @template x = zeros(5)
           x[1:5] := Dirichlet(ones(2))
       end

In this example, which could arise when e.g. storing the prior distributions of variables, we don't ever want to have a scenario where we collect a prior Dirichlet(ones(2)) that cannot be associated with a value x[1:5].

The thing is, though, that can never happen anyway. If you were to write x[1:5] ~ Dirichlet(ones(2)), when you evaluate the model, this will fail anyway because rand(Dirichlet(...)) will return a length-2 vector, which cannot then be set into x[1:5]. (This is completely independent of any VNTs and will fail even with an empty OnlyAccsVarInfo that has no accumulators.)

In my opinion, then, the size check when setting items in VNTs is superfluous because it is impossible to construct a model that will both run correctly and lead to inconsistent VNTs. Inconsistent VNTs only arise from models that cannot be run.

Conclusion

If not for the issue with pointwise logprobs, I would have probably just let it slide and kept the size check in even though IMO it doesn't accomplish much. However, given that there is actually a motivation to remove it, I decided that it's better to remove it.

Because of this, several structs in DynamicPPL that used to store size information now no longer need to. These include RangeAndLinked, VectorValue, and LinkedVectorValue. I have therefore also removed those fields.

I'm currently somewhere around 95% sure that this is the right thing to do. However, I've decided to separate this into another PR just to cover for that 5% chance, in case I need to revert it in the future.

Previous discussion about the need for size checks

See #1180 (comment).

Given that this was a point of disagreement before, I feel obliged to offer a bit more explanation.

Philosophically I still agree that for a completely generic data structure / container, it makes sense that you cannot assign a value to x[1:5] unless the value does indeed have size (5,). However, I think in this PR I am rejecting the premise of that argument. Specifically, the use of VNT is so closely tied to a DPPL model (and especially more so since templates / shadow arrays were implemented), that I don't think that VNT is truly a generic data structure, and I think it's okay to rely on point (2) above, and to sacrifice some purity for pragmatism.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 21, 2026

Benchmark Report

  • this PR's head: 2f247e353842a0cf7290ebf556704f17f8e53d35
  • base branch: 63588db81cd37211d4c0856c2b43aef17f166d90

Computer Information

Julia Version 1.11.9
Commit 53a02c0720c (2026-02-06 00:27 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 4 × AMD EPYC 7763 64-Core Processor
  WORD_SIZE: 64
  LLVM: libLLVM-16.0.6 (ORCJIT, znver3)
Threads: 1 default, 0 interactive, 1 GC (on 4 virtual cores)

Benchmark Results

┌───────────────────────┬───────┬─────────────┬────────┬───────────────────────────────┬────────────────────────────┬─────────────────────────────────┐
│                       │       │             │        │       t(eval) / t(ref)        │     t(grad) / t(eval)      │        t(grad) / t(ref)         │
│                       │       │             │        │ ─────────┬──────────┬──────── │ ───────┬─────────┬──────── │ ──────────┬───────────┬──────── │
│                 Model │   Dim │  AD Backend │ Linked │     base │  this PR │ speedup │   base │ this PR │ speedup │      base │   this PR │ speedup │
├───────────────────────┼───────┼─────────────┼────────┼──────────┼──────────┼─────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│               Dynamic │    10 │    mooncake │   true │   394.72 │   421.77 │    0.94 │  15.03 │    9.85 │    1.53 │   5930.71 │   4154.74 │    1.43 │
│                   LDA │    12 │ reversediff │   true │  2674.67 │  2754.60 │    0.97 │   4.98 │    4.78 │    1.04 │  13326.24 │  13159.71 │    1.01 │
│   Loop univariate 10k │ 10000 │    mooncake │   true │ 65221.09 │ 67087.08 │    0.97 │   5.21 │    5.18 │    1.01 │ 339950.65 │ 347813.71 │    0.98 │
├───────────────────────┼───────┼─────────────┼────────┼──────────┼──────────┼─────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│    Loop univariate 1k │  1000 │    mooncake │   true │  6553.03 │  6657.48 │    0.98 │   5.12 │    5.19 │    0.99 │  33527.03 │  34524.24 │    0.97 │
│      Multivariate 10k │ 10000 │    mooncake │   true │ 33256.25 │ 33976.26 │    0.98 │   9.96 │    9.91 │    1.00 │ 331083.74 │ 336675.72 │    0.98 │
│       Multivariate 1k │  1000 │    mooncake │   true │  3171.95 │  3643.13 │    0.87 │  14.53 │    9.23 │    1.57 │  46092.49 │  33618.32 │    1.37 │
├───────────────────────┼───────┼─────────────┼────────┼──────────┼──────────┼─────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│ Simple assume observe │     1 │ forwarddiff │  false │     2.75 │     2.69 │    1.02 │   3.77 │    4.04 │    0.93 │     10.38 │     10.90 │    0.95 │
│           Smorgasbord │   201 │ forwarddiff │  false │  1116.47 │  1123.03 │    0.99 │ 131.47 │   68.22 │    1.93 │ 146780.58 │  76613.79 │    1.92 │
│           Smorgasbord │   201 │      enzyme │   true │  1532.94 │  1546.70 │    0.99 │   5.55 │    7.30 │    0.76 │   8510.54 │  11296.96 │    0.75 │
├───────────────────────┼───────┼─────────────┼────────┼──────────┼──────────┼─────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│           Smorgasbord │   201 │ forwarddiff │   true │  1522.59 │  1558.61 │    0.98 │  63.97 │   68.50 │    0.93 │  97398.06 │ 106765.58 │    0.91 │
│           Smorgasbord │   201 │    mooncake │   true │  1528.20 │  1543.19 │    0.99 │   5.39 │    5.52 │    0.98 │   8244.51 │   8512.25 │    0.97 │
│           Smorgasbord │   201 │ reversediff │   true │  1530.31 │  1545.99 │    0.99 │  97.39 │  100.88 │    0.97 │ 149036.44 │ 155964.62 │    0.96 │
├───────────────────────┼───────┼─────────────┼────────┼──────────┼──────────┼─────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤
│              Submodel │     1 │    mooncake │   true │     3.41 │     3.56 │    0.96 │  51.88 │   48.85 │    1.06 │    176.70 │    173.77 │    1.02 │
└───────────────────────┴───────┴─────────────┴────────┴──────────┴──────────┴─────────┴────────┴─────────┴─────────┴───────────┴───────────┴─────────┘

@codecov
Copy link

codecov bot commented Feb 21, 2026

Codecov Report

❌ Patch coverage is 96.29630% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.78%. Comparing base (63588db) to head (2f247e3).
⚠️ Report is 1 commits behind head on breaking.

Files with missing lines Patch % Lines
src/transformed_values.jl 77.77% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           breaking    #1281      +/-   ##
============================================
+ Coverage     78.76%   78.78%   +0.01%     
============================================
  Files            47       47              
  Lines          3589     3582       -7     
============================================
- Hits           2827     2822       -5     
+ Misses          762      760       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Contributor

DynamicPPL.jl documentation for PR #1281 is available at:
https://TuringLang.github.io/DynamicPPL.jl/previews/PR1281/

Base automatically changed from py/templates-in-observe to breaking February 21, 2026 13:30
@penelopeysm penelopeysm merged commit d7d3b31 into breaking Feb 21, 2026
21 checks passed
@penelopeysm penelopeysm deleted the py/no-vnt-size branch February 21, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant