Wrapping models¶
Replace nn.Linear / nn.Conv2d with packed equivalents.
wrap_model ¶
wrap_model(
model: Module,
mode: WrapMode | str | None = None,
*,
policy: WrapPolicy = "hybrid_ffn",
skip_name_substr: Iterable[str] | None = None,
min_in_features: int = 64,
min_out_features: int = 0,
skip_attn: bool = True,
calib: CalibConfig | None = None,
inplace: bool = True,
accuracy_first: bool = False,
exclude_exact: Iterable[str] | None = None,
force_narrow: bool = False,
fuse_bn: bool = False,
drop_in_threshold: float = 0.85,
) -> tuple[nn.Module, WrapReport]
Product wrap API with hybrid / aggressive / ternary_wo / auto policies.
mode=None means unspecified (default binary_xnor, or recommender when
policy='auto' / mode='auto').
exclude_exact: full dotted module names to never wrap (sensitivity).
force_narrow: allow binary_xnor on shapes guardrails would refuse.
fuse_bn: fold Linear+BN1d / BiReal BN before packing (W3.T09).
Source code in bnn/wrap/api.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
wrap_linear_modules ¶
wrap_linear_modules(
model: Module,
mode: WrapMode = "binary_xnor",
*,
skip_name_substr: Iterable[str] = DEFAULT_SKIP,
min_in_features: int = 64,
min_out_features: int = 0,
calib: CalibConfig | None = None,
inplace: bool = True,
) -> tuple[nn.Module, WrapReport]
Legacy API: skip-list based wrap (still used by demos/tests).
Source code in bnn/wrap/api.py
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
model_param_bytes ¶
model_param_bytes(model: Module) -> dict
Source code in bnn/wrap/api.py
247 248 249 250 | |
Packed modules¶
PackedBinaryXNORLinear ¶
Bases: Module
Inference Linear: packed ±1 weights + signed activations → XNOR GEMM.
Weights are packed once at construction and cached on the module.
Source code in bnn/wrap/packed_linear.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
gemm_only ¶
gemm_only(x_pm1: ndarray) -> np.ndarray
Microbench: x already ±1 float (B, N); uses cached packed weights.
Source code in bnn/wrap/packed_linear.py
160 161 162 163 164 165 166 167 168 | |
TernaryWeightOnlyLinear ¶
Bases: Module
Accurate-first weight-only ternary (FP activations, FP GEMM after dequant).
Source code in bnn/wrap/packed_linear.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
BinaryWeightOnlyDequantLinear ¶
Bases: Module
Source code in bnn/wrap/packed_linear.py
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | |
PackedBinaryConv2d ¶
Bases: Module
Packed ±1 Conv2d weights (size win). Forward = dequant + F.conv2d.
Thesis: this is a size path (uint64 pack of ±1 kernels), not an XNOR
popcount Conv claim. Packed words live in weight_packed_i64 for
.bnnpack / state_dict round-trips (W5.T09).
Source code in bnn/wrap/packed_linear.py
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
Policy, calibration, guardrails¶
recommend_wrap_policy ¶
recommend_wrap_policy(
layer: Linear | None = None,
hw: HardwareInfo | None = None,
*,
accuracy_first: bool = False,
) -> PolicyDecision
Recommend wrap mode/policy for a layer (or globally if layer is None).
Source code in bnn/wrap/policy.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |
CalibConfig
dataclass
¶
Source code in bnn/wrap/calibrate.py
24 25 26 27 28 29 | |
calibrate_linear_scales ¶
calibrate_linear_scales(
weight: Tensor,
*,
cfg: CalibConfig | None = None,
activation_batches: list[Tensor] | None = None,
) -> Tensor
Return alpha/scale for a Linear weight.
If activation_batches is provided, optionally blend with activation
absmean — still weight-primary for PTQ wrap.
Activation nudge (honest): when per_channel scales are used, the
act factor is a global scalar sqrt(mean(|act|)) clamped to
[0.5, 2.0] and multiplied onto every channel. It is not per-token or
per-channel activation calibration — only a mild distribution nudge so
absmean weight scales are not wildly off under atypical input ranges.
Source code in bnn/wrap/calibrate.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
check_linear_wrap_guardrails ¶
check_linear_wrap_guardrails(
lin: Linear,
*,
mode: str = "binary_xnor",
force: bool = False,
) -> GuardrailVerdict
Return whether wrapping this Linear is advisable.
Hard-refuse binary XNOR on pathologically narrow dims. Widths below
MIN_WIDTH_BINARY_EFFICIENT remain allowed when the caller set a low
min_in_features (demos / pedagogy) — efficiency tip only.
Source code in bnn/wrap/guardrails.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | |
light_qat_recover ¶
light_qat_recover(
model: Module,
calib_x: Tensor,
*,
teacher: Module | None = None,
steps: int = 50,
lr: float = 0.001,
layer_names: list[str] | None = None,
loss_fn: Callable[[Tensor, Tensor], Tensor]
| None = None,
logit_loss: LogitLoss = "kd",
temperature: float = 2.0,
fold_alpha: bool = True,
train_targets_only: bool = False,
hidden_mse: float = 0.0,
binarize_activations: bool = True,
sign_mode: SignMode | None = None,
) -> dict
Short STE fine-tune on named Linears (default: modules named ffn / mlp).
If teacher is given, distill via logit_loss (kd / mse / cosine);
else requires loss_fn. Learned STE alpha is folded into restored
Linear magnitudes by default so wrap calib matches QAT.
Source code in bnn/wrap/qat.py
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | |
score_layer_sensitivity ¶
score_layer_sensitivity(
model: Module,
calib_inputs: Tensor,
*,
mode: ScoreMode = "binary_xnor",
policy: str = "all_large_linear",
min_in_features: int = 32,
min_out_features: int = 0,
drop_in_threshold: float = 0.85,
skip_fragile: bool = True,
fragile_drop: float = 0.05,
calib: CalibConfig | None = None,
) -> SensitivityReport
Score each eligible Linear by cosine drop when wrapped alone.
fragile_drop: if baseline_cosine - layer_cosine >= fragile_drop,
suggest skip (also if cosine falls below drop_in_threshold).
Source code in bnn/wrap/sensitivity.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
search_layer_modes ¶
search_layer_modes(
model: Module,
calib_inputs: Tensor,
*,
quality_floor: float = 0.9,
policy: str = "all_large_linear",
min_in_features: int = 32,
min_out_features: int = 0,
calib: CalibConfig | None = None,
max_relaxations: int | None = None,
) -> ModeSearchReport
Pick binary / ternary / skip per layer to maximise theoretical
compression while keeping measured output cosine at or above
quality_floor (W3.T06).
Strategy: start from the most aggressive assignment (everything binary), then repeatedly relax the single layer that is costing the most quality — binary → ternary → skip — remeasuring the whole model each time. Relaxing greedily by measured damage is what makes this better than a per-layer threshold: layer interactions only show up in the joint measurement.
Cost is O(L) probes in the common case rather than the 3**L of an
exhaustive search, which is why it stays usable on real stacks.
Source code in bnn/wrap/sensitivity.py
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 | |
ModeSearchReport
dataclass
¶
Result of the per-layer binary / ternary / skip search (W3.T06).
Source code in bnn/wrap/sensitivity.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | |
compression ¶
compression() -> float
Theoretical weight compression over the searched layers only.
Source code in bnn/wrap/sensitivity.py
218 219 220 221 222 223 224 | |
SearchAssignment
dataclass
¶
Chosen mode for one Linear, with the evidence behind the choice.
Source code in bnn/wrap/sensitivity.py
180 181 182 183 184 185 186 187 188 189 190 191 192 | |