FullSupportBarDistribution.sample()/cdf()/icdf() disagree with forward() in outer buckets
Summary
FullSupportBarDistribution.forward() evaluates the first and last components as
half-normal tails with support on all of R, but cdf(), icdf(), and sample()
inherit the finite, within-bin-uniform implementation from BarDistribution.
Consequently, values returned by sample() are not distributed according to the
density returned by -forward(). The same inconsistency affects tail quantiles,
median(), and methods such as ucb() that call the inherited icdf().
This is still present in PriorLabs/TabPFN commit
930ea728b40f7f321d965250838bca8507bd70b9 (2026-08-20):
FullSupportBarDistribution.forward() adds the half-normal tail density.
FullSupportBarDistribution.sample() calls self.icdf(...).
icdf() is inherited from BarDistribution and linearly interpolates between
the two finite borders of every selected bucket, including both outer buckets.
Minimal reproduction
import torch
try:
from tabpfn.architectures.shared.bar_distribution import FullSupportBarDistribution
except ImportError:
from tabpfn.model.bar_distribution import FullSupportBarDistribution
torch.manual_seed(7)
dist = FullSupportBarDistribution(torch.tensor([-2.0, -1.0, 1.0, 2.0]))
probs = torch.tensor([0.25, 0.50, 0.25])
logits = probs.log().repeat(20_000, 1)
samples = dist.sample(logits)
buckets = dist.map_to_bucket_idx(samples).clamp(0, dist.num_bars - 1)
reported_logq = -dist(logits, samples)
sample_logq = probs[buckets].log() - dist.bucket_widths[buckets].log()
edge = (buckets == 0) | (buckets == dist.num_bars - 1)
print(samples.min().item(), samples.max().item())
print(((samples < -2.0) | (samples > 2.0)).sum().item())
print((reported_logq[edge] - sample_logq[edge]).min().item())
print((reported_logq[edge] - sample_logq[edge]).max().item())
Observed:
sample range: approximately [-2, 2]
outside [-2, 2]: 0 / 20,000
reported_logq - sample_logq in edge buckets: [-0.847, -0.620]
The documented full-support construction puts half of each outer component's
mass beyond its finite outer border. With probabilities [0.25, 0.50, 0.25],
the expected outside fraction is therefore
0.5 * (0.25 + 0.25) = 0.25,
or about 5,000 of 20,000 samples, rather than zero.
The complete reproducible audit also compares empirical and analytic CDFs. It
obtains KS distance 0.00585 to the inherited finite ICDF distribution versus
0.125 to the density implemented by forward().
Expected behavior
For borders b[0] < ... < b[K] and component probabilities pi, the two outer
components should be sampled from the same half-normal densities used by
forward():
left: Y = b[1] - HalfNormal(scale_left)
right: Y = b[K-1] + HalfNormal(scale_right)
where each scale keeps half of the component mass inside its nominal outer bin:
scale_left = (b[1] - b[0]) / HalfNormal(1).icdf(0.5)
scale_right = (b[K] - b[K - 1]) / HalfNormal(1).icdf(0.5)
For a uniform variate u that selects the left component, with conditional
rank r = u / pi[0], the inverse CDF should be
b[1] - HalfNormal(scale_left).icdf(1 - r).
For the right component it should be
b[K-1] + HalfNormal(scale_right).icdf(r).
Interior buckets should retain their current within-bin-uniform interpolation.
Requested fix
- Override
cdf() and icdf() in FullSupportBarDistribution with the
half-normal formulas already used by forward().
- Make
sample() use that corrected inverse CDF, or sample the selected outer
components directly from their half-normal distributions.
- Preserve batch shape, device, and dtype; avoid rebuilding the result through
torch.tensor(list_of_tensors), which can move CUDA results back to CPU.
- Keep the existing borders, component probabilities, and half-normal scale
convention so checkpoint likelihoods remain unchanged.
- Audit inherited methods that depend on
cdf()/icdf(), including
quantile(), median(), ucb(), and border-probability translation.
- State the affected releases and include the correction in the release notes.
Proposed regression tests
- Deterministic inverse-CDF checkpoints. For borders
[-2,-1,1,2] and
probabilities [0.25,0.50,0.25], verify
icdf(0.125) == -2 and icdf(0.875) == 2 within numerical tolerance.
The current inherited implementation returns -1.5 and 1.5.
- CDF/ICDF round trip. Check
cdf(logits, icdf(logits, u)) == u for ranks
inside both tails and all interior buckets.
- Sampling tail mass. A seeded large sample should place approximately 25%
of samples outside [-2,2] in the reproduction above.
- Density/sampling consistency. A goodness-of-fit test should accept the
distribution defined by forward() and reject the finite inherited-ICDF law.
- Device/dtype coverage. Test CPU and CUDA when available, including batched
logits and float32/float64.
Why this matters
The mismatch invalidates any use that assumes samples and density values refer
to the same distribution, including importance sampling, Monte Carlo
expectations, density estimation, generative sampling, and calibrated tail
quantiles. For importance sampling it can additionally violate the proposal
support condition because the implemented sampler has finite support while the
reported proposal density is full-support.
FullSupportBarDistribution.sample()/cdf()/icdf() disagree with forward() in outer buckets
Summary
FullSupportBarDistribution.forward()evaluates the first and last components ashalf-normal tails with support on all of
R, butcdf(),icdf(), andsample()inherit the finite, within-bin-uniform implementation from
BarDistribution.Consequently, values returned by
sample()are not distributed according to thedensity returned by
-forward(). The same inconsistency affects tail quantiles,median(), and methods such asucb()that call the inheritedicdf().This is still present in PriorLabs/TabPFN commit
930ea728b40f7f321d965250838bca8507bd70b9(2026-08-20):FullSupportBarDistribution.forward()adds the half-normal tail density.FullSupportBarDistribution.sample()callsself.icdf(...).icdf()is inherited fromBarDistributionand linearly interpolates betweenthe two finite borders of every selected bucket, including both outer buckets.
Minimal reproduction
Observed:
The documented full-support construction puts half of each outer component's
mass beyond its finite outer border. With probabilities
[0.25, 0.50, 0.25],the expected outside fraction is therefore
or about 5,000 of 20,000 samples, rather than zero.
The complete reproducible audit also compares empirical and analytic CDFs. It
obtains KS distance
0.00585to the inherited finite ICDF distribution versus0.125to the density implemented byforward().Expected behavior
For borders
b[0] < ... < b[K]and component probabilitiespi, the two outercomponents should be sampled from the same half-normal densities used by
forward():where each scale keeps half of the component mass inside its nominal outer bin:
For a uniform variate
uthat selects the left component, with conditionalrank
r = u / pi[0], the inverse CDF should beFor the right component it should be
Interior buckets should retain their current within-bin-uniform interpolation.
Requested fix
cdf()andicdf()inFullSupportBarDistributionwith thehalf-normal formulas already used by
forward().sample()use that corrected inverse CDF, or sample the selected outercomponents directly from their half-normal distributions.
torch.tensor(list_of_tensors), which can move CUDA results back to CPU.convention so checkpoint likelihoods remain unchanged.
cdf()/icdf(), includingquantile(),median(),ucb(), and border-probability translation.Proposed regression tests
[-2,-1,1,2]andprobabilities
[0.25,0.50,0.25], verifyicdf(0.125) == -2andicdf(0.875) == 2within numerical tolerance.The current inherited implementation returns
-1.5and1.5.cdf(logits, icdf(logits, u)) == ufor ranksinside both tails and all interior buckets.
of samples outside
[-2,2]in the reproduction above.distribution defined by
forward()and reject the finite inherited-ICDF law.logits and float32/float64.
Why this matters
The mismatch invalidates any use that assumes samples and density values refer
to the same distribution, including importance sampling, Monte Carlo
expectations, density estimation, generative sampling, and calibrated tail
quantiles. For importance sampling it can additionally violate the proposal
support condition because the implemented sampler has finite support while the
reported proposal density is full-support.