nrfunc 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nrfunc/__init__.py +44 -0
- nrfunc/_rand.py +35 -0
- nrfunc/core/__init__.py +15 -0
- nrfunc/core/auto_alloc.py +266 -0
- nrfunc/core/functionalize.py +512 -0
- nrfunc/core/region.py +45 -0
- nrfunc/io/__init__.py +8 -0
- nrfunc/io/binary.py +231 -0
- nrfunc/io/serialize.py +69 -0
- nrfunc/io/sharding.py +88 -0
- nrfunc/utils/__init__.py +10 -0
- nrfunc/utils/benchmark.py +78 -0
- nrfunc/utils/value.py +144 -0
- nrfunc-1.0.0.dist-info/METADATA +553 -0
- nrfunc-1.0.0.dist-info/RECORD +18 -0
- nrfunc-1.0.0.dist-info/WHEEL +5 -0
- nrfunc-1.0.0.dist-info/licenses/LICENSE +21 -0
- nrfunc-1.0.0.dist-info/top_level.txt +1 -0
nrfunc/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""nrfunc:神经区函数算法 —— 成熟模型的「函数化 + 生成物」库。
|
|
2
|
+
|
|
3
|
+
职责边界(明确,本库只做两件事):
|
|
4
|
+
|
|
5
|
+
上游:你已训练好的成熟模型(任意框架),抽出一层权重矩阵即可。
|
|
6
|
+
本库:
|
|
7
|
+
1. 函数化 —— 把权重按「区域函数」重新表达(几何/功能/结构分区 + 0/1 阶函数)
|
|
8
|
+
2. 生成物 —— 区域函数参数 + 归属索引 + 量化打包 + 分片存储 + 价值对比
|
|
9
|
+
下游:你自己负责把「生成物」接入推理 / 部署。
|
|
10
|
+
|
|
11
|
+
本库不做(这些属于上游):训练、模型定义、蒸馏、微调、量化感知训练(QAT)。
|
|
12
|
+
"""
|
|
13
|
+
from .core import (importance_by_norm, split_regions, kmeans, kmeans_cosine,
|
|
14
|
+
assign_rows,
|
|
15
|
+
regionify, regionify_hierarchical, reconstruct,
|
|
16
|
+
functional_forward, explained_variance,
|
|
17
|
+
regionify_transformer_block,
|
|
18
|
+
auto_alloc, default_K_grid, default_r_grid)
|
|
19
|
+
from .io import (quantize_weights, dequantize_weights, packed_bytes,
|
|
20
|
+
ShardedRegionStore, build_store, shard_placement,
|
|
21
|
+
to_bytes, from_bytes)
|
|
22
|
+
from .utils import (bench_single, bench_batch, rss_mb, set_threads, device_of, logf,
|
|
23
|
+
size_bytes_raw, size_bytes_regionalized, index_bits,
|
|
24
|
+
compare_value, cpu_time, gpu_memory_bytes)
|
|
25
|
+
|
|
26
|
+
__version__ = '1.0.0'
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
# 区划分
|
|
30
|
+
'split_regions', 'importance_by_norm',
|
|
31
|
+
# 函数化
|
|
32
|
+
'kmeans', 'kmeans_cosine', 'assign_rows', 'regionify', 'regionify_hierarchical',
|
|
33
|
+
'reconstruct', 'functional_forward', 'explained_variance',
|
|
34
|
+
'regionify_transformer_block',
|
|
35
|
+
'auto_alloc', 'default_K_grid', 'default_r_grid',
|
|
36
|
+
# 生成物 · 序列化 / 分散存储 / 二进制字节块
|
|
37
|
+
'quantize_weights', 'dequantize_weights', 'packed_bytes',
|
|
38
|
+
'ShardedRegionStore', 'build_store', 'shard_placement',
|
|
39
|
+
'to_bytes', 'from_bytes',
|
|
40
|
+
# 生成物 · 价值证明 / 性能基准
|
|
41
|
+
'bench_single', 'bench_batch', 'rss_mb', 'set_threads', 'device_of', 'logf',
|
|
42
|
+
'size_bytes_raw', 'size_bytes_regionalized', 'index_bits',
|
|
43
|
+
'compare_value', 'cpu_time', 'gpu_memory_bytes',
|
|
44
|
+
]
|
nrfunc/_rand.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""确定性随机(内置 PRNG,保证可复现)。
|
|
2
|
+
|
|
3
|
+
库内所有需要随机的环节(聚类初始质心、种子派生)统一走 mulberry32 /
|
|
4
|
+
fisher_yates,保证同一 seed 跨次运行、跨平台结果一致。
|
|
5
|
+
"""
|
|
6
|
+
import math
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def mulberry32(seed):
|
|
10
|
+
"""mulberry32 确定性 PRNG,返回无参函数 ``rand() -> [0, 1)``。
|
|
11
|
+
|
|
12
|
+
保证同一 seed 跨次运行、跨平台结果一致。
|
|
13
|
+
"""
|
|
14
|
+
a = seed & 0xFFFFFFFF
|
|
15
|
+
|
|
16
|
+
def rand():
|
|
17
|
+
nonlocal a
|
|
18
|
+
a = (a + 0x6D2B79F5) & 0xFFFFFFFF
|
|
19
|
+
x = (a ^ (a >> 15)) & 0xFFFFFFFF
|
|
20
|
+
t = (x * (1 | a)) & 0xFFFFFFFF
|
|
21
|
+
y = (t ^ (t >> 7)) & 0xFFFFFFFF
|
|
22
|
+
t = ((t + (y * (61 | t)) & 0xFFFFFFFF) ^ t) & 0xFFFFFFFF
|
|
23
|
+
t = (t ^ (t >> 14)) & 0xFFFFFFFF
|
|
24
|
+
return t / 4294967296.0
|
|
25
|
+
|
|
26
|
+
return rand
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def fisher_yates(n, rand):
|
|
30
|
+
"""Fisher-Yates 洗牌,返回打乱后的下标列表 [0, n)。"""
|
|
31
|
+
idx = list(range(n))
|
|
32
|
+
for i in range(n - 1, 0, -1):
|
|
33
|
+
j = int(math.floor(rand() * (i + 1)))
|
|
34
|
+
idx[i], idx[j] = idx[j], idx[i]
|
|
35
|
+
return idx
|
nrfunc/core/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""core:区划分 + 函数化(神经区函数算法的核心算子)。"""
|
|
2
|
+
from .region import importance_by_norm, split_regions
|
|
3
|
+
from .functionalize import (kmeans, kmeans_cosine, assign_rows, regionify,
|
|
4
|
+
regionify_hierarchical, reconstruct,
|
|
5
|
+
functional_forward, explained_variance,
|
|
6
|
+
regionify_transformer_block)
|
|
7
|
+
from .auto_alloc import auto_alloc, default_K_grid, default_r_grid
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'importance_by_norm', 'split_regions',
|
|
11
|
+
'kmeans', 'kmeans_cosine', 'assign_rows', 'regionify', 'regionify_hierarchical',
|
|
12
|
+
'reconstruct', 'functional_forward', 'explained_variance',
|
|
13
|
+
'regionify_transformer_block',
|
|
14
|
+
'auto_alloc', 'default_K_grid', 'default_r_grid',
|
|
15
|
+
]
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""自适应函数化分配器(auto_alloc):给每一层自动挑「函数化配置」,或判「跳过」。
|
|
2
|
+
|
|
3
|
+
这是 nrfunc「函数化」能力的自适应前端:不训练、不蒸馏、不碰 QAT,只对已训练好的
|
|
4
|
+
权重,在「内存净省」+「掉点可容忍」两个约束下,自动决定这一层:
|
|
5
|
+
* 用不用函数化(不划算就跳过)
|
|
6
|
+
* 用 0 阶(质心)还是 1 阶(低秩)
|
|
7
|
+
* K(分区数)取多少、r(主成分数)取多少
|
|
8
|
+
|
|
9
|
+
为什么需要它(P3 v0.8 立项动机):
|
|
10
|
+
1 阶低秩「净省内存」是有条件的(近似充要条件 K×(1+r) < n)。给小层 / 深层
|
|
11
|
+
无脑上同一套 K、r 会内存反涨(实测反例:小 MLP fc1 多 158%、小 CNN conv1
|
|
12
|
+
多 73%、大 CNN 最深卷积层多 25%)。auto_alloc 逐层搜索,只保留「净省」的
|
|
13
|
+
配置,从机制上杜绝反涨。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import math
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from .functionalize import assign_rows, regionify
|
|
22
|
+
from ..utils import size_bytes_raw, size_bytes_regionalized, index_bits
|
|
23
|
+
|
|
24
|
+
__all__ = ['auto_alloc', 'default_K_grid', 'default_r_grid']
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _size_after_est(n, D, K, r, order, bits):
|
|
28
|
+
"""纯解析式预估函数化后字节数(无需跑 regionify)。
|
|
29
|
+
|
|
30
|
+
由 size_bytes_regionalized 的公式直接推出:regionify 结果中 components
|
|
31
|
+
必为 (K, r)、coeffs 必为 (n, r),故函数化后字节数只由 (n, D, K, r, order)
|
|
32
|
+
决定,可在跑昂贵聚类/SVD 之前精确预估,用作净省先验剪枝。
|
|
33
|
+
"""
|
|
34
|
+
ibits = index_bits(n, K)
|
|
35
|
+
if order == 0:
|
|
36
|
+
fn_params = K * D
|
|
37
|
+
else:
|
|
38
|
+
fn_params = K * D + K * r * D + n * r
|
|
39
|
+
return math.ceil(fn_params * bits / 8) + math.ceil(n * ibits / 8)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def default_K_grid(n):
|
|
43
|
+
"""默认 K(分区数)候选网格:2 的幂(≤ n),并补一个 sqrt(n) 附近参考值。"""
|
|
44
|
+
if n < 2:
|
|
45
|
+
return [1]
|
|
46
|
+
grid = []
|
|
47
|
+
k = 2
|
|
48
|
+
while k <= n:
|
|
49
|
+
grid.append(k)
|
|
50
|
+
k *= 2
|
|
51
|
+
s = int(round(np.sqrt(n)))
|
|
52
|
+
if s >= 2 and s not in grid:
|
|
53
|
+
grid.append(s)
|
|
54
|
+
return sorted(set(grid))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def default_r_grid():
|
|
58
|
+
"""默认 r(1 阶每区主成分数)候选网格。"""
|
|
59
|
+
return [1, 2, 4, 8]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def auto_alloc(rows, signal='G', activations=None, groups=None,
|
|
63
|
+
budget=None, tol_ev=None, eval_fn=None,
|
|
64
|
+
fidelity_margin=0.3,
|
|
65
|
+
K_grid=None, r_grid=None, r_max=None,
|
|
66
|
+
iters=60, seed=0, float_bits=32):
|
|
67
|
+
"""给一层权重自动挑最优函数化配置,或判「跳过」(保持原样)。
|
|
68
|
+
|
|
69
|
+
参数:
|
|
70
|
+
rows:(n, D) 被函数化的权重行向量(n=单元数,D=每单元参数量)。
|
|
71
|
+
signal:'G' 几何 / 'F' 功能(需 activations)/ 'S' 结构(需 groups)。
|
|
72
|
+
activations:signal='F' 时 (n, T) 激活响应矩阵。
|
|
73
|
+
groups:signal='S' 时 (n,) 每个单元的结构归属(层号/头号/通道号)。
|
|
74
|
+
budget:可选,函数化后字节上限。给定时该层目标从「净省」收紧为
|
|
75
|
+
「after ≤ budget」;若原权重 before ≤ budget 则该层直接判跳过。
|
|
76
|
+
tol_ev:可选,重构保真度(explained_variance)下限。注意:explained 是
|
|
77
|
+
「重构误差」代理,跨信号不可比(S 结构分区 explained 天然偏低但分类
|
|
78
|
+
掉点低),故默认不启用;掉点约束更可靠的手段是 eval_fn。
|
|
79
|
+
eval_fn:可选,真实精度回调 eval_fn(result) -> bool,True=接受该候选。
|
|
80
|
+
这是「掉点 < 阈值」的可靠实现(库不内置数据/前向,交给使用方)。
|
|
81
|
+
fidelity_margin:默认 0.3。相对保真保护——在「净省」候选中,排除重构
|
|
82
|
+
保真度(explained)比同层最优候选低 0.3 以上的,防止为多省一点内存而
|
|
83
|
+
把保真砸穿。S 结构分区 explained 天然偏低、相对差距小,故不误伤;
|
|
84
|
+
传 None 关闭(退化为纯内存口径「省最多」)。
|
|
85
|
+
K_grid / r_grid:可选候选网格。默认 K=2 的幂≤n(补 sqrt(n)),r=[1,2,4,8]。
|
|
86
|
+
r_max:可选,1 阶主成分数上限(对 r_grid 截断)。
|
|
87
|
+
iters / seed:聚类迭代与随机种子(固定 seed 保证可复现)。
|
|
88
|
+
float_bits:内存口径位宽,默认 32(f32 常驻,与「内存·函数部署」口径一致)。
|
|
89
|
+
|
|
90
|
+
返回 dict:
|
|
91
|
+
plan:(order, K, r) 三元组;None 表示「跳过」(该层保持原样,最诚实)。
|
|
92
|
+
decision:'order1' / 'order0' / 'skip'
|
|
93
|
+
result:regionify 返回 dict(跳过时为 None)
|
|
94
|
+
order / K / r:选中的阶数与参数(跳过时均为 None)
|
|
95
|
+
bytes_before / bytes_after:函数化前后内存字节(float_bits 口径)
|
|
96
|
+
saving:节省率(正=省,负=多;跳过=0.0)
|
|
97
|
+
explained:重构保真度(跳过时为 None)
|
|
98
|
+
n_candidates:通过全部约束的候选数
|
|
99
|
+
n_tried:实际跑过 regionify 的候选总数
|
|
100
|
+
skipped_reason:跳过原因(仅 decision='skip' 时非空)
|
|
101
|
+
"""
|
|
102
|
+
rows = np.asarray(rows, dtype=np.float64)
|
|
103
|
+
n, D = rows.shape
|
|
104
|
+
bytes_before = size_bytes_raw(n, D, float_bits)
|
|
105
|
+
|
|
106
|
+
# —— 预算语义:原权重已达标则无需函数化(省事且保精度)——
|
|
107
|
+
if budget is not None and bytes_before <= budget:
|
|
108
|
+
return _skip(bytes_before, 0, '原权重已满足内存预算(before ≤ budget)')
|
|
109
|
+
|
|
110
|
+
# —— 候选网格 ——
|
|
111
|
+
if signal == 'S':
|
|
112
|
+
groups_arr = np.asarray(groups)
|
|
113
|
+
if groups_arr.size == 0:
|
|
114
|
+
raise ValueError("signal='S'(结构分区)需提供非空 groups")
|
|
115
|
+
K_vals = [int(len(np.unique(groups_arr)))] # 结构边界固定 K,不搜
|
|
116
|
+
else:
|
|
117
|
+
K_vals = [int(k) for k in (K_grid if K_grid is not None else default_K_grid(n))]
|
|
118
|
+
|
|
119
|
+
r_vals = [int(r) for r in (r_grid if r_grid is not None else default_r_grid())]
|
|
120
|
+
if r_max is not None:
|
|
121
|
+
r_vals = [r for r in r_vals if r <= r_max]
|
|
122
|
+
|
|
123
|
+
# —— 逐候选构造模型。同一 K 的分区只聚类一次:regionify 的聚类阶段对
|
|
124
|
+
# 同 K、同 seed 结果逐位相同,重复跑是纯浪费(原来每 K 跑 1+len(r_vals) 次)——
|
|
125
|
+
candidates = []
|
|
126
|
+
n_tried = 0
|
|
127
|
+
assign_cache = {}
|
|
128
|
+
for order in (1, 0):
|
|
129
|
+
for K in K_vals:
|
|
130
|
+
if K > n:
|
|
131
|
+
continue
|
|
132
|
+
if order == 0:
|
|
133
|
+
if K >= n: # K=n 退化为逐单元无压缩,必然不省
|
|
134
|
+
continue
|
|
135
|
+
# 精确净省预判:预估字节 ≥ 原字节则必然不省,跳过(省去聚类)
|
|
136
|
+
if _size_after_est(n, D, K, None, 0, float_bits) >= bytes_before:
|
|
137
|
+
continue
|
|
138
|
+
assign = _get_assign(assign_cache, rows, K, signal,
|
|
139
|
+
iters, seed, activations, groups)
|
|
140
|
+
res = regionify(rows, signal=signal, K=K, order=0,
|
|
141
|
+
iters=iters, seed=seed,
|
|
142
|
+
activations=activations, groups=groups,
|
|
143
|
+
assign=assign)
|
|
144
|
+
n_tried += 1
|
|
145
|
+
candidates.append(_candidate(res, order, K, None,
|
|
146
|
+
bytes_before, float_bits))
|
|
147
|
+
else: # order == 1
|
|
148
|
+
for r in r_vals:
|
|
149
|
+
if r < 1:
|
|
150
|
+
continue
|
|
151
|
+
# 先验剪枝:1 阶净省的必要条件是 K×(1+r) < n(否则必然反涨)
|
|
152
|
+
if K * (1 + r) >= n:
|
|
153
|
+
continue
|
|
154
|
+
# 精确净省预判:预估字节 ≥ 原字节则必然不省,跳过(省去 SVD)
|
|
155
|
+
if _size_after_est(n, D, K, r, 1, float_bits) >= bytes_before:
|
|
156
|
+
continue
|
|
157
|
+
assign = _get_assign(assign_cache, rows, K, signal,
|
|
158
|
+
iters, seed, activations, groups)
|
|
159
|
+
res = regionify(rows, signal=signal, K=K, order=1, r=r,
|
|
160
|
+
iters=iters, seed=seed,
|
|
161
|
+
activations=activations, groups=groups,
|
|
162
|
+
assign=assign)
|
|
163
|
+
n_tried += 1
|
|
164
|
+
candidates.append(_candidate(res, order, K, r,
|
|
165
|
+
bytes_before, float_bits))
|
|
166
|
+
|
|
167
|
+
# —— 硬约束:内存净省(绝不反涨)——
|
|
168
|
+
kept = [c for c in candidates if c['bytes_after'] < bytes_before]
|
|
169
|
+
if not kept:
|
|
170
|
+
return _skip(bytes_before, n_tried,
|
|
171
|
+
'候选网格内不存在任何净省配置(该层冗余度不足,函数化必反涨)')
|
|
172
|
+
|
|
173
|
+
# —— 可选:内存预算 ——
|
|
174
|
+
if budget is not None:
|
|
175
|
+
kept = [c for c in kept if c['bytes_after'] <= budget]
|
|
176
|
+
if not kept:
|
|
177
|
+
return _skip(bytes_before, n_tried, '存在净省候选,但均超出内存预算 budget')
|
|
178
|
+
|
|
179
|
+
# —— 掉点代理:默认相对保真保护 + 可选绝对 tol_ev ——
|
|
180
|
+
if fidelity_margin is not None:
|
|
181
|
+
ev_max = max(c['explained'] for c in kept)
|
|
182
|
+
kept = [c for c in kept if c['explained'] >= ev_max - fidelity_margin]
|
|
183
|
+
if not kept:
|
|
184
|
+
return _skip(bytes_before, n_tried,
|
|
185
|
+
f'存在净省候选,但均未通过相对保真保护(fidelity_margin={fidelity_margin})')
|
|
186
|
+
if tol_ev is not None:
|
|
187
|
+
kept = [c for c in kept if c['explained'] >= tol_ev]
|
|
188
|
+
if not kept:
|
|
189
|
+
return _skip(bytes_before, n_tried,
|
|
190
|
+
f'存在净省候选,但重构保真度均低于 tol_ev={tol_ev}')
|
|
191
|
+
|
|
192
|
+
# —— 真实精度(可选,最可靠)——
|
|
193
|
+
# 早停优化:最终选「省最多、并列取保真最高」,故按 (saving, explained) 降序
|
|
194
|
+
# 逐个评估,第一个通过 eval_fn 的即全局最优,其余候选无需再跑昂贵精度评估。
|
|
195
|
+
if eval_fn is not None:
|
|
196
|
+
ordered = sorted(kept, key=lambda c: (-c['saving'], -c['explained']))
|
|
197
|
+
passed = None
|
|
198
|
+
for c in ordered:
|
|
199
|
+
if bool(eval_fn(c['result'])):
|
|
200
|
+
passed = c
|
|
201
|
+
break
|
|
202
|
+
if passed is None:
|
|
203
|
+
return _skip(bytes_before, n_tried, '存在净省候选,但均未通过 eval_fn 真实精度约束')
|
|
204
|
+
best = passed
|
|
205
|
+
else:
|
|
206
|
+
# 选「省最多」,并列取「保真最高」(explained 越大 = 掉点越小)
|
|
207
|
+
best = max(kept, key=lambda c: (c['saving'], c['explained']))
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
'plan': (best['order'], best['K'], best['r']),
|
|
211
|
+
'decision': 'order1' if best['order'] == 1 else 'order0',
|
|
212
|
+
'result': best['result'],
|
|
213
|
+
'order': best['order'],
|
|
214
|
+
'K': best['K'],
|
|
215
|
+
'r': best['r'],
|
|
216
|
+
'bytes_before': bytes_before,
|
|
217
|
+
'bytes_after': best['bytes_after'],
|
|
218
|
+
'saving': best['saving'],
|
|
219
|
+
'explained': best['explained'],
|
|
220
|
+
'n_candidates': len(kept),
|
|
221
|
+
'n_tried': n_tried,
|
|
222
|
+
'skipped_reason': None,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _get_assign(cache, rows, K, signal, iters, seed, activations, groups):
|
|
227
|
+
"""按 K 取缓存的聚类归属;未缓存才跑一次聚类(同 K 全程只跑一次)。"""
|
|
228
|
+
assign = cache.get(K)
|
|
229
|
+
if assign is None:
|
|
230
|
+
assign, _ = assign_rows(rows, signal=signal, K=K, iters=iters, seed=seed,
|
|
231
|
+
activations=activations, groups=groups)
|
|
232
|
+
cache[K] = assign
|
|
233
|
+
return assign
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _candidate(result, order, K, r, bytes_before, float_bits):
|
|
237
|
+
"""把一次 regionify 结果封装成候选,算好字节与节省率。"""
|
|
238
|
+
after = size_bytes_regionalized(result, bits=float_bits)
|
|
239
|
+
saving = round((bytes_before - after) / bytes_before, 4)
|
|
240
|
+
return {
|
|
241
|
+
'result': result,
|
|
242
|
+
'order': order,
|
|
243
|
+
'K': K,
|
|
244
|
+
'r': r,
|
|
245
|
+
'bytes_after': after,
|
|
246
|
+
'saving': saving,
|
|
247
|
+
'explained': result.get('explained'),
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _skip(bytes_before, n_tried, reason):
|
|
252
|
+
return {
|
|
253
|
+
'plan': None,
|
|
254
|
+
'decision': 'skip',
|
|
255
|
+
'result': None,
|
|
256
|
+
'order': None,
|
|
257
|
+
'K': None,
|
|
258
|
+
'r': None,
|
|
259
|
+
'bytes_before': bytes_before,
|
|
260
|
+
'bytes_after': None,
|
|
261
|
+
'saving': 0.0,
|
|
262
|
+
'explained': None,
|
|
263
|
+
'n_candidates': 0,
|
|
264
|
+
'n_tried': n_tried,
|
|
265
|
+
'skipped_reason': reason,
|
|
266
|
+
}
|