captum's ShapleyValueSampling and ShapleyValues build the running attribution accumulator
total_attrib with a hardcoded dtype=torch.float, that is float32, regardless of the model's output
dtype. Its sibling FeatureAblation, in the same PerturbationAttribution family and driven by the
same forward function, reads the forward output dtype into attrib_type and builds its accumulator
with dtype=attrib_type. On a float64 model the Shapley accumulator is silently float32 while the
FeatureAblation one is float64. The Shapley values are therefore accumulated in float32: always a silent
precision downgrade, and under a large activation dynamic range a 100 percent violation of the Shapley
completeness axiom sum(attributions) = f(x) - f(baseline) that does not converge with more samples.
captum, captum/attr/_core/shapley_value.py, 0.9.0 and current main (unfixed), the accumulator in
both the main path (0.9.0 line 367, main line 373) and the futures path (0.9.0 line 639, main line 653):
total_attrib = [
torch.zeros(
tuple(output_shape) + tuple(input.shape[1:]),
dtype=torch.float, # hardcoded float32, ignores the forward dtype
device=inputs_tuple[0].device,
)
for input in inputs_tuple
]The sibling captum/attr/_core/feature_ablation.py (lines 71, 79) does it correctly:
attrib_type = flattened_initial_eval.dtype # read from the forward output
...
total_attrib = [
torch.zeros((n_outputs,) + input.shape[1:], dtype=attrib_type, device=input.device)
for input in inputs
]Each Shapley marginal f(prefix + i) - f(prefix) is formed in the model's dtype, then stored into the
float32 accumulator. The fix is one substitution, dtype=attrib_type, exactly as the sibling does.
Driving the real captum methods on a float64 model:
always-on downgrade (float64 linear model):
ShapleyValues dtype torch.float32, abs err 5.96e-08
FeatureAblation dtype torch.float64, abs err 0.00e+00
completeness violation (A=1e8 model):
f(x) - f(base) = 2.0
exact Shapley [1.0, 1.0] sum 2.0
captum Shapley [1.0, 0.0] sum 1.0 gap 1.0
sampling does not converge toward [1, 1]:
n_samples= 25: ['-4.000e+06', '4.000e+06']
n_samples= 1000: ['-2.000e+05', '2.000e+05']
n_samples=20000: ['-4.100e+05', '4.100e+05']
Always on, a float64 model returns float32 Shapley values carrying float32 rounding, here 5.96e-08
(float32 machine epsilon), while FeatureAblation on the same model returns exact float64. Under a large
dynamic range, f(x0, x1) = A*x0 + A*x1 - (2A-2)*x0*x1 with A = 1e8, the true attribution of each
feature is 1.0 and their sum equals f(x) - f(baseline) = 2.0. The marginals are of magnitude A,
so once they are stored in the float32 accumulator the O(1) attribution is lost: ShapleyValues, a
full permutation enumeration with no sampling, returns [1.0, 0.0] summing to 1.0, and
ShapleyValueSampling wanders at magnitude 1e5 to 1e7 and does not shrink toward [1, 1] as the
sample count grows.
The base case isolates the fault: FeatureAblation uses the same base class and the same forward
function and returns the float64 answer exactly, so the loss is the Shapley accumulator dtype, not the
perturbation approach. The accumulator.py model reproduces the permutation accumulation with the
accumulator dtype as the only free variable: a float64 accumulator gives [1, 1] with completeness gap
0, a float32 accumulator collapses it, everything else held fixed.
The downgrade is on the default path with no special parameters: any float64 model attributed with
ShapleyValues or ShapleyValueSampling gets float32 accumulation, whereas FeatureAblation,
Occlusion, and the other perturbation methods honor float64. For float32 models the accumulator dtype
coincidentally matches, so this is invisible; it is float64 models specifically, common in scientific
and calibration settings, that silently lose precision, and lose the attribution entirely when the
marginals are large relative to their telescoping sum. The error is an induced float64 to float32
downcast, not unavoidable round-off: the sibling avoids it and is exact.
excerpt.py: the Shapley accumulator line and the FeatureAblation accumulator line quoted with the flags that name the fault.accumulator.py: the Shapley permutation accumulation with the accumulator dtype as a parameter, so a float64 total is exact and a float32 total collapses, with everything else identical.consequence.py: the real captumShapleyValues,ShapleyValueSampling, andFeatureAblation, the always-on dtype downgrade, the completeness violation, and the non-convergence of the sampler.test_shapcast.py: the model float64 accumulator is exact and the float32 one is not, the real Shapley is downgraded while FeatureAblation stays exact, completeness is violated, and the sampler does not converge.
python accumulator.py
python consequence.py
python test_shapcast.py
The accumulator lines are quoted from current main; the attributions are produced by the real captum
methods. The fix is dtype=attrib_type in both accumulator constructions, matching FeatureAblation.