CompressedGemma commited on
Commit
786693e
·
verified ·
1 Parent(s): 8131e8c

DS tweaks

Browse files
Files changed (1) hide show
  1. slimshard.py +162 -164
slimshard.py CHANGED
@@ -3051,40 +3051,10 @@ def export_safetensors_to_gguf(
3051
  gname = f"blk.{lidx}.{mapped_proj}"
3052
  work_items.append(("experts", (lidx, proj), members, gname))
3053
 
3054
- # Stream quantized/converted tensor payloads into a temporary data file and record GGUF directory
3055
- tmp_data = tempfile.NamedTemporaryFile(delete=False, dir=str(out_path.parent))
3056
- gguf_tensors: list[tuple[str, tuple[int, ...], int, int]] = []
3057
- data_offset = 0
3058
- payload_cache: dict[tuple, tuple[int, tuple[int, ...], int, int]] = {}
3059
- stats = {"tensors": 0, "q2_k": 0, "q4_0": 0, "bf16": 0, "f32": 0}
3060
-
3061
- if is_dsv4:
3062
- last_l = max(int(arch.get("num_hidden_layers", 40)) - 1, 0)
3063
- fn_k = f"layers.{last_l}.hc_ffn_fn"
3064
- bs_k = f"layers.{last_l}.hc_ffn_base"
3065
- sc_k = f"layers.{last_l}.hc_ffn_scale"
3066
- if fn_k in keys_set and bs_k in keys_set and sc_k in keys_set:
3067
- fn_t = f.get_tensor(fn_k).float()[:hc_mult].contiguous()
3068
- bs_t = f.get_tensor(bs_k).float()[:hc_mult].contiguous()
3069
- sc_t = f.get_tensor(sc_k).float()[:1].contiguous()
3070
- for g_hc_name, hc_t in (
3071
- ("output_hc_fn.weight", fn_t),
3072
- ("output_hc_base.weight", bs_t),
3073
- ("output_hc_scale.weight", sc_t),
3074
- ):
3075
- pad = _gguf_align(data_offset) - data_offset
3076
- if pad > 0:
3077
- tmp_data.write(b"\x00" * pad)
3078
- data_offset += pad
3079
- raw_hc = hc_t.cpu().numpy().astype("<f4").tobytes()
3080
- gguf_tensors.append((g_hc_name, tuple(reversed(hc_t.shape)), _GGML_F32, data_offset))
3081
- tmp_data.write(raw_hc)
3082
- data_offset += len(raw_hc)
3083
- stats["tensors"] += 1
3084
- stats["f32"] += 1
3085
- tmp_data.flush()
3086
-
3087
- total_items = len(work_items)
3088
 
3089
  def _dequant_single_to_f32_torch(w_key: str, s_key: str | None) -> "torch.Tensor":
3090
  wt = f.get_tensor(w_key).to(dev)
@@ -3113,7 +3083,6 @@ def export_safetensors_to_gguf(
3113
  if not is_emb and row_w % QK_K == 0 and n_el % QK_K == 0:
3114
  return "Q2_K"
3115
  return "BF16"
3116
- # mixed
3117
  if is_emb:
3118
  return "Q4_0" if (row_w % QK4_0 == 0 and n_el % QK4_0 == 0) else "BF16"
3119
  if row_w % QK_K == 0 and n_el % QK_K == 0:
@@ -3122,6 +3091,18 @@ def export_safetensors_to_gguf(
3122
  return "Q4_0"
3123
  return "BF16"
3124
 
 
 
 
 
 
 
 
 
 
 
 
 
3125
  def _encode_tensor_f32(t_f32: "torch.Tensor", tgt: str) -> tuple[int, bytes]:
3126
  row_w = int(t_f32.shape[-1])
3127
  if tgt == "F32":
@@ -3145,157 +3126,174 @@ def export_safetensors_to_gguf(
3145
  return _GGML_Q2_K, blocks_to_bytes(res.scales, res.qs, res.dm)
3146
  raise ValueError(tgt)
3147
 
3148
- for idx_w, item in enumerate(work_items, 1):
3149
- if stop_event is not None and stop_event.is_set():
3150
- tmp_data.close()
3151
- os.unlink(tmp_data.name)
3152
- raise InterruptedError("GGUF export cancelled by user.")
3153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3154
  kind = item[0]
 
3155
  if kind == "single":
3156
  _, w_key, s_key, gname = item
3157
- sl = f.get_slice(w_key)
3158
- t_shape = tuple(sl.get_shape())
3159
  if is_dsv4 and gname.endswith(".attn_output_a.weight") and len(t_shape) == 2 and t_shape[0] == o_groups * o_lora_rank:
3160
  t_shape = (o_groups, o_lora_rank, t_shape[1])
3161
  g_dims = tuple(reversed(t_shape)) if t_shape else (1,)
3162
  tgt = _choose_gguf_target(gname, t_shape or (1,))
3163
-
3164
- # Fast signature for identical anchor layer reuse
3165
- lm = LAYER_RE.match(w_key)
3166
- cache_key = None
3167
- if lm:
3168
- suf = lm.group(3)
3169
- w_Sample = _payload_bytes(f.get_tensor(w_key).flatten()[:256])
3170
- cache_key = ("single", suf, t_shape, tgt, w_Sample)
3171
- if cache_key is not None and cache_key in payload_cache:
3172
- out_type, g_dims, src_off, src_len = payload_cache[cache_key]
3173
- raw_bytes = os.pread(tmp_data.fileno(), src_len, src_off)
3174
- elif len(t_shape) == 2 and t_shape[0] > 4096 and s_key is None and tgt in ("BF16", "Q4_0"):
3175
- # Stream large embedding/head matrices in 4096-row chunks to keep memory < 100 MB
3176
- pad = _gguf_align(data_offset) - data_offset
3177
- if pad > 0:
3178
- tmp_data.write(b"\x00" * pad)
3179
- data_offset += pad
3180
- cur_off = data_offset
3181
- written_len = 0
3182
- out_type = _GGML_BF16 if tgt == "BF16" else _GGML_Q4_0
3183
- for r0 in range(0, t_shape[0], 4096):
3184
- r1 = min(r0 + 4096, t_shape[0])
3185
- sub_t = sl[r0:r1].to(dev)
3186
- if tgt == "BF16":
3187
- b_part = sub_t.to(torch.bfloat16).view(torch.uint16).cpu().numpy().astype("<u2").tobytes()
3188
- else:
3189
- sub_np = sub_t.float().cpu().numpy().astype(np.float32)
3190
- b_part, _, _ = quantize_tensor_q4_0(sub_np, cfg=hpc_cfg)
3191
- tmp_data.write(b_part)
3192
- written_len += len(b_part)
3193
- del sub_t
3194
- tmp_data.flush()
3195
- data_offset += written_len
3196
- gguf_tensors.append((gname, g_dims, out_type, cur_off))
3197
- stats["tensors"] += 1
3198
- stats["bf16" if out_type == _GGML_BF16 else "q4_0"] += 1
3199
- if progress_cb is not None:
3200
- progress_cb(idx_w, total_items, gname, tgt, stats)
3201
- continue
3202
- else:
3203
- t_f32 = _dequant_single_to_f32_torch(w_key, s_key)
3204
- if not t_shape:
3205
- t_f32 = t_f32.reshape(1)
3206
- out_type, raw_bytes = _encode_tensor_f32(t_f32, tgt)
3207
- del t_f32
3208
  else:
3209
  _, (lidx, proj), members, gname = item
3210
  first_w = members[0][1]
3211
  e_shape = tuple(f.get_slice(first_w).get_shape())
3212
- # If packed FP4 I8, logical in_dim is 2 * stored in_dim
3213
  dt_e = _safetensors_dtype(f.get_slice(first_w).get_dtype())
3214
  if dt_e == "I8" and members[0][2] is not None:
3215
  e_shape = (e_shape[0], e_shape[1] * 2)
3216
  full_shape = (len(members), e_shape[0], e_shape[1])
3217
  g_dims = (e_shape[1], e_shape[0], len(members))
3218
  tgt = _choose_gguf_target(gname, full_shape)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3219
 
3220
- w_sample = _payload_bytes(f.get_tensor(first_w).flatten()[:256])
3221
- cache_key = ("experts", proj, full_shape, tgt, w_sample)
3222
- if cache_key in payload_cache:
3223
- out_type, g_dims, src_off, src_len = payload_cache[cache_key]
3224
- raw_bytes = os.pread(tmp_data.fileno(), src_len, src_off)
 
 
3225
  else:
3226
- chunks = []
3227
- for _, wk, sk in members:
3228
- ef32 = _dequant_single_to_f32_torch(wk, sk)
3229
- _, eb = _encode_tensor_f32(ef32, tgt)
3230
- chunks.append(eb)
3231
- del ef32
3232
- raw_bytes = b"".join(chunks)
3233
- out_type = (
3234
- _GGML_Q2_K
3235
- if tgt == "Q2_K"
3236
- else (_GGML_Q4_0 if tgt == "Q4_0" else (_GGML_BF16 if tgt == "BF16" else _GGML_F32))
3237
- )
3238
 
3239
- pad = _gguf_align(data_offset) - data_offset
3240
- if pad > 0:
3241
- tmp_data.write(b"\x00" * pad)
3242
- data_offset += pad
3243
- cur_off = data_offset
3244
- tmp_data.write(raw_bytes)
3245
- tmp_data.flush()
3246
- data_offset += len(raw_bytes)
3247
-
3248
- if cache_key is not None and cache_key not in payload_cache:
3249
- payload_cache[cache_key] = (out_type, g_dims, cur_off, len(raw_bytes))
3250
-
3251
- gguf_tensors.append((gname, g_dims, out_type, cur_off))
3252
- stats["tensors"] += 1
3253
- if out_type == _GGML_Q2_K:
3254
- stats["q2_k"] += 1
3255
- elif out_type == _GGML_Q4_0:
3256
- stats["q4_0"] += 1
3257
- elif out_type == _GGML_BF16:
3258
- stats["bf16"] += 1
3259
- else:
3260
- stats["f32"] += 1
3261
-
3262
- if progress_cb is not None and (idx_w % 8 == 0 or idx_w == total_items):
3263
- progress_cb(idx_w, total_items, gname, tgt, stats)
3264
-
3265
- # Update effective expert count in arch metadata if stacked experts were pruned
3266
- if expert_groups:
3267
- first_group = next(iter(expert_groups.values()))
3268
- arch["num_experts"] = len(first_group)
3269
-
3270
- kv_pairs = _build_gguf_kv_pairs(
3271
- arch,
3272
- tok_path if tok_path.exists() else None,
3273
- chat_path if chat_path.exists() else None,
3274
- quant_format,
3275
- st_path.stem,
3276
- )
3277
 
3278
- with out_path.open("wb") as fout:
3279
- fout.write(struct.pack("<IIQQ", _GGUF_MAGIC, 3, len(gguf_tensors), len(kv_pairs)))
3280
- for k, vt, raw_v in kv_pairs:
3281
- fout.write(_pack_gguf_str(k))
3282
- fout.write(struct.pack("<I", vt))
3283
- fout.write(raw_v)
3284
- for gname, g_dims, out_type, off in gguf_tensors:
3285
- fout.write(_pack_gguf_str(gname))
3286
- fout.write(struct.pack("<I", len(g_dims)))
3287
- for d in g_dims:
3288
- fout.write(struct.pack("<Q", int(d)))
3289
- fout.write(struct.pack("<I", int(out_type)))
3290
- fout.write(struct.pack("<Q", int(off)))
3291
- data_start = _gguf_align(fout.tell())
3292
- if data_start > fout.tell():
3293
- fout.write(b"\x00" * (data_start - fout.tell()))
3294
- tmp_data.seek(0)
3295
- shutil.copyfileobj(tmp_data, fout, length=16 * 1024 * 1024)
3296
-
3297
- tmp_data.close()
3298
- os.unlink(tmp_data.name)
3299
  stats["output_bytes"] = out_path.stat().st_size
3300
  if verbose:
3301
  print(
 
3051
  gname = f"blk.{lidx}.{mapped_proj}"
3052
  work_items.append(("experts", (lidx, proj), members, gname))
3053
 
3054
+ # Update effective expert count in arch metadata if stacked experts were pruned
3055
+ if expert_groups:
3056
+ first_group = next(iter(expert_groups.values()))
3057
+ arch["num_experts"] = len(first_group)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3058
 
3059
  def _dequant_single_to_f32_torch(w_key: str, s_key: str | None) -> "torch.Tensor":
3060
  wt = f.get_tensor(w_key).to(dev)
 
3083
  if not is_emb and row_w % QK_K == 0 and n_el % QK_K == 0:
3084
  return "Q2_K"
3085
  return "BF16"
 
3086
  if is_emb:
3087
  return "Q4_0" if (row_w % QK4_0 == 0 and n_el % QK4_0 == 0) else "BF16"
3088
  if row_w % QK_K == 0 and n_el % QK_K == 0:
 
3091
  return "Q4_0"
3092
  return "BF16"
3093
 
3094
+ def _target_type_and_bytes(tgt: str, torch_shape: tuple[int, ...]) -> tuple[int, int]:
3095
+ n_el = int(np.prod(torch_shape)) if torch_shape else 1
3096
+ if tgt == "F32":
3097
+ return _GGML_F32, n_el * 4
3098
+ if tgt == "BF16":
3099
+ return _GGML_BF16, n_el * 2
3100
+ if tgt == "Q4_0":
3101
+ return _GGML_Q4_0, (n_el // QK4_0) * Q4_0_BLOCK_BYTES
3102
+ if tgt == "Q2_K":
3103
+ return _GGML_Q2_K, (n_el // QK_K) * 84
3104
+ raise ValueError(tgt)
3105
+
3106
  def _encode_tensor_f32(t_f32: "torch.Tensor", tgt: str) -> tuple[int, bytes]:
3107
  row_w = int(t_f32.shape[-1])
3108
  if tgt == "F32":
 
3126
  return _GGML_Q2_K, blocks_to_bytes(res.scales, res.qs, res.dm)
3127
  raise ValueError(tgt)
3128
 
3129
+ # Precompute exact GGUF tensor directory and offsets in O(1) so we write in a single pass
3130
+ gguf_tensors: list[tuple[str, tuple[int, ...], int, int]] = []
3131
+ planned_exec: list[tuple] = []
3132
+ data_offset = 0
 
3133
 
3134
+ if is_dsv4:
3135
+ last_l = max(int(arch.get("num_hidden_layers", 40)) - 1, 0)
3136
+ fn_k = f"layers.{last_l}.hc_ffn_fn"
3137
+ bs_k = f"layers.{last_l}.hc_ffn_base"
3138
+ sc_k = f"layers.{last_l}.hc_ffn_scale"
3139
+ if fn_k in keys_set and bs_k in keys_set and sc_k in keys_set:
3140
+ fn_t = f.get_tensor(fn_k).float()[:hc_mult].contiguous()
3141
+ bs_t = f.get_tensor(bs_k).float()[:hc_mult].contiguous()
3142
+ sc_t = f.get_tensor(sc_k).float()[:1].contiguous()
3143
+ for g_hc_name, hc_t in (
3144
+ ("output_hc_fn.weight", fn_t),
3145
+ ("output_hc_base.weight", bs_t),
3146
+ ("output_hc_scale.weight", sc_t),
3147
+ ):
3148
+ data_offset = _gguf_align(data_offset)
3149
+ raw_hc = hc_t.cpu().numpy().astype("<f4").tobytes()
3150
+ g_dims = tuple(reversed(hc_t.shape))
3151
+ gguf_tensors.append((g_hc_name, g_dims, _GGML_F32, data_offset))
3152
+ planned_exec.append(("raw_bytes", g_hc_name, _GGML_F32, data_offset, raw_hc))
3153
+ data_offset += len(raw_hc)
3154
+
3155
+ for item in work_items:
3156
  kind = item[0]
3157
+ data_offset = _gguf_align(data_offset)
3158
  if kind == "single":
3159
  _, w_key, s_key, gname = item
3160
+ t_shape = tuple(f.get_slice(w_key).get_shape())
 
3161
  if is_dsv4 and gname.endswith(".attn_output_a.weight") and len(t_shape) == 2 and t_shape[0] == o_groups * o_lora_rank:
3162
  t_shape = (o_groups, o_lora_rank, t_shape[1])
3163
  g_dims = tuple(reversed(t_shape)) if t_shape else (1,)
3164
  tgt = _choose_gguf_target(gname, t_shape or (1,))
3165
+ out_type, out_sz = _target_type_and_bytes(tgt, t_shape or (1,))
3166
+ gguf_tensors.append((gname, g_dims, out_type, data_offset))
3167
+ planned_exec.append(("single", w_key, s_key, gname, t_shape, g_dims, tgt, out_type, out_sz, data_offset))
3168
+ data_offset += out_sz
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3169
  else:
3170
  _, (lidx, proj), members, gname = item
3171
  first_w = members[0][1]
3172
  e_shape = tuple(f.get_slice(first_w).get_shape())
 
3173
  dt_e = _safetensors_dtype(f.get_slice(first_w).get_dtype())
3174
  if dt_e == "I8" and members[0][2] is not None:
3175
  e_shape = (e_shape[0], e_shape[1] * 2)
3176
  full_shape = (len(members), e_shape[0], e_shape[1])
3177
  g_dims = (e_shape[1], e_shape[0], len(members))
3178
  tgt = _choose_gguf_target(gname, full_shape)
3179
+ out_type, out_sz = _target_type_and_bytes(tgt, full_shape)
3180
+ gguf_tensors.append((gname, g_dims, out_type, data_offset))
3181
+ planned_exec.append(("experts", (lidx, proj), members, gname, full_shape, g_dims, tgt, out_type, out_sz, data_offset))
3182
+ data_offset += out_sz
3183
+
3184
+ kv_pairs = _build_gguf_kv_pairs(
3185
+ arch,
3186
+ tok_path if tok_path.exists() else None,
3187
+ chat_path if chat_path.exists() else None,
3188
+ quant_format,
3189
+ st_path.stem,
3190
+ )
3191
+
3192
+ payload_cache: dict[tuple, bytes] = {}
3193
+ stats = {"tensors": 0, "q2_k": 0, "q4_0": 0, "bf16": 0, "f32": 0}
3194
+ total_items = len(planned_exec)
3195
+
3196
+ with out_path.open("wb") as fout:
3197
+ fout.write(struct.pack("<IIQQ", _GGUF_MAGIC, 3, len(gguf_tensors), len(kv_pairs)))
3198
+ for k, vt, raw_v in kv_pairs:
3199
+ fout.write(_pack_gguf_str(k))
3200
+ fout.write(struct.pack("<I", vt))
3201
+ fout.write(raw_v)
3202
+ for gname, g_dims, out_type, off in gguf_tensors:
3203
+ fout.write(_pack_gguf_str(gname))
3204
+ fout.write(struct.pack("<I", len(g_dims)))
3205
+ for d in g_dims:
3206
+ fout.write(struct.pack("<Q", int(d)))
3207
+ fout.write(struct.pack("<I", int(out_type)))
3208
+ fout.write(struct.pack("<Q", int(off)))
3209
+ data_start = _gguf_align(fout.tell())
3210
+ if data_start > fout.tell():
3211
+ fout.write(b"\x00" * (data_start - fout.tell()))
3212
+
3213
+ for idx_w, pitem in enumerate(planned_exec, 1):
3214
+ if stop_event is not None and stop_event.is_set():
3215
+ raise InterruptedError("GGUF export cancelled by user.")
3216
+
3217
+ pkind = pitem[0]
3218
+ if pkind == "raw_bytes":
3219
+ _, gname, out_type, cur_off, raw_bytes = pitem
3220
+ tgt = "F32"
3221
+ target_pos = data_start + cur_off
3222
+ if target_pos > fout.tell():
3223
+ fout.write(b"\x00" * (target_pos - fout.tell()))
3224
+ fout.write(raw_bytes)
3225
+ elif pkind == "single":
3226
+ _, w_key, s_key, gname, t_shape, g_dims, tgt, out_type, out_sz, cur_off = pitem
3227
+ target_pos = data_start + cur_off
3228
+ if target_pos > fout.tell():
3229
+ fout.write(b"\x00" * (target_pos - fout.tell()))
3230
+
3231
+ lm = LAYER_RE.match(w_key)
3232
+ cache_key = None
3233
+ if lm:
3234
+ suf = lm.group(3)
3235
+ w_sample = _payload_bytes(f.get_tensor(w_key).flatten()[:256])
3236
+ cache_key = ("single", suf, t_shape, tgt, w_sample)
3237
+ if cache_key is not None and cache_key in payload_cache:
3238
+ fout.write(payload_cache[cache_key])
3239
+ elif len(t_shape) == 2 and t_shape[0] > 4096 and s_key is None and tgt in ("BF16", "Q4_0"):
3240
+ sl = f.get_slice(w_key)
3241
+ for r0 in range(0, t_shape[0], 4096):
3242
+ r1 = min(r0 + 4096, t_shape[0])
3243
+ sub_t = sl[r0:r1].to(dev)
3244
+ if tgt == "BF16":
3245
+ b_part = sub_t.to(torch.bfloat16).view(torch.uint16).cpu().numpy().astype("<u2").tobytes()
3246
+ else:
3247
+ sub_np = sub_t.float().cpu().numpy().astype(np.float32)
3248
+ b_part, _, _ = quantize_tensor_q4_0(sub_np, cfg=hpc_cfg)
3249
+ fout.write(b_part)
3250
+ del sub_t
3251
+ else:
3252
+ t_f32 = _dequant_single_to_f32_torch(w_key, s_key)
3253
+ if not t_shape:
3254
+ t_f32 = t_f32.reshape(1)
3255
+ elif tuple(t_f32.shape) != t_shape:
3256
+ t_f32 = t_f32.reshape(t_shape)
3257
+ _, raw_bytes = _encode_tensor_f32(t_f32, tgt)
3258
+ del t_f32
3259
+ fout.write(raw_bytes)
3260
+ if cache_key is not None:
3261
+ payload_cache[cache_key] = raw_bytes
3262
+ else:
3263
+ _, (lidx, proj), members, gname, full_shape, g_dims, tgt, out_type, out_sz, cur_off = pitem
3264
+ target_pos = data_start + cur_off
3265
+ if target_pos > fout.tell():
3266
+ fout.write(b"\x00" * (target_pos - fout.tell()))
3267
+
3268
+ first_w = members[0][1]
3269
+ w_sample = _payload_bytes(f.get_tensor(first_w).flatten()[:256])
3270
+ cache_key = ("experts", proj, full_shape, tgt, w_sample)
3271
+ if cache_key in payload_cache:
3272
+ fout.write(payload_cache[cache_key])
3273
+ else:
3274
+ chunks = []
3275
+ for _, wk, sk in members:
3276
+ ef32 = _dequant_single_to_f32_torch(wk, sk)
3277
+ _, eb = _encode_tensor_f32(ef32, tgt)
3278
+ chunks.append(eb)
3279
+ del ef32
3280
+ raw_bytes = b"".join(chunks)
3281
+ fout.write(raw_bytes)
3282
+ payload_cache[cache_key] = raw_bytes
3283
 
3284
+ stats["tensors"] += 1
3285
+ if out_type == _GGML_Q2_K:
3286
+ stats["q2_k"] += 1
3287
+ elif out_type == _GGML_Q4_0:
3288
+ stats["q4_0"] += 1
3289
+ elif out_type == _GGML_BF16:
3290
+ stats["bf16"] += 1
3291
  else:
3292
+ stats["f32"] += 1
 
 
 
 
 
 
 
 
 
 
 
3293
 
3294
+ if progress_cb is not None and (idx_w % 8 == 0 or idx_w == total_items):
3295
+ progress_cb(idx_w, total_items, gname, tgt, stats)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3297
  stats["output_bytes"] = out_path.stat().st_size
3298
  if verbose:
3299
  print(