Skip to main content

Rust CLI for inspecting ML model artifacts without loading the framework

Project description

mod-trace

Inspect ML model artifacts without loading the framework.

mod-trace is a small Rust CLI for answering a practical question:

What is inside this model file?

It can inspect real artifacts such as CatBoost .cbm files, LightGBM .txt/.lgb text models, ONNX .onnx graphs, and PyTorch .pt/.pth checkpoints, then report structure, size, parameters, operator mix, rough inference cost, and changes between versions. All formats are read natively — no Python, framework, or runtime needed (CatBoost --deep is the one optional exception). The PyTorch reader is static: it sizes/names tensors and fingerprints weights without decoding exact shapes.

The most useful command is explain-diff, which says in plain English what changed between two model versions:

mod-trace explain-diff old_model.onnx new_model.onnx

(A secondary "tensor lab" for handcrafted JSON plans lives in docs/tensor-lab.md.)

Install

mod-trace is a single self-contained binary (written in Rust, published to PyPI as a wheel). The core commands need no Python and no ML framework at runtime.

pipx install mod-trace      # recommended: isolated, puts `mod-trace` on your PATH
# or
pip install mod-trace

mod-trace doctor            # shows which formats and helpers are available
  • Prebuilt wheels for Apple Silicon macOS, Linux x86_64, and Windows x64pip just downloads the binary, no build step.
  • Intel macOS has no prebuilt wheel yet, so pip install there builds from source (needs a Rust toolchain).
  • Optional: CatBoost --deep (decoded tree/leaf diffs) shells out to Python catboost; point MODELLENS_PYTHON at a Python that has it if needed. Everything else — including all of LightGBM, ONNX, and PyTorch — needs nothing extra.

Core Commands

cargo run -- doctor
cargo run -- doctor --json
cargo run -- inspect path/to/model.cbm
cargo run -- inspect --json path/to/model.cbm
cargo run -- inspect --deep path/to/model.cbm
cargo run -- inspect path/to/model.onnx
cargo run -- inspect --json path/to/model.onnx
cargo run -- explain path/to/model.onnx
cargo run -- diff path/to/old_model.cbm path/to/new_model.cbm
cargo run -- diff --json path/to/old_model.cbm path/to/new_model.cbm
cargo run -- diff --deep path/to/old_model.cbm path/to/new_model.cbm
cargo run -- check --max-size-growth 20% --fail-on-feature-change path/to/old_model.cbm path/to/new_model.cbm
cargo run -- diff path/to/old_model.onnx path/to/new_model.onnx
cargo run -- diff --json path/to/old_model.onnx path/to/new_model.onnx
cargo run -- check --max-ops-growth 25% --fail-on-new-op path/to/old_model.onnx path/to/new_model.onnx

Installed binary form:

mod-trace doctor
mod-trace doctor --json
mod-trace inspect model.cbm
mod-trace inspect --json model.cbm
mod-trace inspect --deep model.cbm
mod-trace inspect model.onnx
mod-trace inspect --json model.onnx
mod-trace explain model.onnx
mod-trace diff old_model.cbm new_model.cbm
mod-trace diff --json old_model.cbm new_model.cbm
mod-trace diff --deep old_model.cbm new_model.cbm
mod-trace explain-diff old_model.onnx new_model.onnx
mod-trace check --max-size-growth 20% --fail-on-feature-change old_model.cbm new_model.cbm
mod-trace inspect model.lgb        # LightGBM (.lgb/.txt), read natively
mod-trace diff old.lgb new.lgb

Why This Exists

Data and ML engineers often inherit model artifacts:

  • fraud_model.cbm
  • ranking_model.onnx
  • model_v17.cbm
  • candidate_model.onnx

Before running them, it is useful to know:

  • what type of model it is
  • how large it is
  • how many trees, parameters, nodes, or operators it contains
  • what the rough per-row or per-forward-pass cost looks like
  • what changed between two versions

mod-trace is not a model runtime. It is an artifact inspector.

Doctor

Check which inspectors and optional helpers are available:

cargo run -- doctor
cargo run -- doctor --json

Example output:

mod-trace Doctor
----------------

Built-in inspectors:
  CatBoost metadata: ok
  ONNX static graph: ok
  JSON tensor plans: ok

Optional Python helpers:
  Python: ok (/path/to/python)
  catboost: ok (1.2.8)

Available commands:
  inspect .cbm/.onnx/.json: available
  diff .cbm/.onnx: available
  inspect --deep .cbm: available
  diff --deep .cbm: available

Use --json when a setup script or CI job needs to check optional helper availability.

JSON Output

Use JSON when mod-trace is part of CI, release checks, or model registry automation:

cargo run -- inspect --json path/to/model.cbm
cargo run -- inspect --json path/to/model.onnx
cargo run -- diff --json path/to/old_model.cbm path/to/new_model.cbm
cargo run -- diff --json path/to/old_model.onnx path/to/new_model.onnx

The JSON diff is designed for checks such as:

  • fail if file size, parameter memory, or estimated ops grows too much
  • fail if CatBoost feature names, training config, or learned-state fingerprint changes unexpectedly
  • fail if ONNX operator counts or initializer tensors change

--deep CatBoost reports are text-only for now because they are diagnostic dumps from CatBoost's native Python parser.

CI Checks

Use check when a model artifact should fail promotion if it changes too much:

cargo run -- check path/to/old_model.cbm path/to/new_model.cbm \
  --max-size-growth 20% \
  --fail-on-feature-change \
  --fail-on-training-config-change

cargo run -- check path/to/old_model.onnx path/to/new_model.onnx \
  --max-size-growth 20% \
  --max-ops-growth 25% \
  --max-parameter-growth 30% \
  --fail-on-new-op

Check rules:

Rule Applies to Fails when
--max-size-growth <pct> all file size grows more than <pct>
--max-parameter-growth <pct> ONNX parameter count grows more than <pct>
--max-ops-growth <pct> ONNX estimated op count grows more than <pct>
--fail-on-new-op ONNX a new operator type appears
--fail-on-feature-change CatBoost, LightGBM feature names change
--fail-on-training-config-change CatBoost, LightGBM objective/learning rate/etc. change

check prints a short PASS/FAIL report and exits nonzero when a rule fails. Any number of rules can be combined; any one failing fails the whole check.

Explain Diff

explain-diff is the plain-English version of diff — it reports what actually changed between two model versions, not just raw numbers:

mod-trace explain-diff old_model.onnx new_model.onnx
Model Change Explanation
------------------------
Type: ONNX
Old: old_model.onnx
New: new_model.onnx

Architecture:
  Attention layers:  12 -> 24
  Hidden size:       768 -> 1024
  Parameters:        110.0M -> 220.0M (+100%)
  Nodes:             420 -> 820 (+95%)

Estimated inference cost (static op proxy): +94%

New operators introduced:
  LayerNormalization

Summary:
  Grew from ~12 to ~24 attention layers; parameters +100%, estimated cost +94%.

Works for ONNX, CatBoost, and LightGBM (tree models report trees / leaves / learned-state instead of attention layers).

CatBoost

Inspect a CatBoost binary model:

cargo run -- inspect path/to/model.cbm
cargo run -- inspect --json path/to/model.cbm
cargo run -- inspect --deep path/to/model.cbm
cargo run -- explain path/to/model.cbm
cargo run -- catboost --deep --limit 10 path/to/model.cbm

--deep is optional and requires Python CatBoost. For a single artifact, it adds exact float/categorical feature typing and float border counts decoded through CatBoost's native parser.

Example output:

CatBoost Model Summary
----------------------
Model: model.cbm
Format: CatBoost binary model (CBM1)
File size: 4.6 MiB

Execution Plan:
  Input row
   |
   v
  Quantize numeric/categorical features
   |
   v
  Traverse symmetric tree ensemble
   |
   v
  Sum leaf values

Estimated Cost:
  Trees / row: 500
  Configured/max split checks / row: 3500
  Max leaf slots: 64000
  Why: 500 trees * depth 7 = 3500 split checks / row

Diff two CatBoost artifacts:

cargo run -- diff path/to/old_model.cbm path/to/new_model.cbm
cargo run -- diff --json path/to/old_model.cbm path/to/new_model.cbm
cargo run -- diff --deep path/to/old_model.cbm path/to/new_model.cbm

The normal diff is fast and reads embedded metadata plus artifact fingerprints. --deep is optional and requires Python CatBoost. It fully decodes the .cbm files through CatBoost's native parser and compares split changes, leaf value changes, leaf weights, feature typing, scale/bias, split type mix, and float-feature border changes keyed by CatBoost's flat/original feature index.

Example output:

Model Diff
----------
Type: CatBoost

Structure:
File size:
  4.6 MiB -> 6.2 MiB (+1677721)
Trees:
  500 -> 650 (+150)
Depth:
  7 -> 8 (+1)
Configured/max split checks / row:
  3500 -> 5200 (+1700)

Parameter-like Internals:
Full artifact fingerprint:
  0x2d2b00dd2375ee48 -> 0xb22f43a2cf612a37 (changed)
Metadata fingerprint:
  0x0e9a08d227179262 -> 0x54a9bd32b5196b8f (changed)
Learned-state fingerprint:
  0xf57e3eeca557d48a -> 0x1390d581269e9dca (changed)
  Note: CatBoost does not expose PyTorch-style parameter tensors here.

Training Config:
Loss:
  RMSE -> RMSE (same)
Learning rate:
  0.100000 -> 0.100000 (same)

Metrics:
Best learn RMSE:
  105.479737 -> 118.574709 (+13.094972)
  Note: learn RMSE increased by 12.41%. Lower is usually better if this metric is comparable.

Features:
Recovered feature names:
  82 -> 83 (+1)

Interpretation:
  Structure changed: inference cost or ensemble shape may differ.
  Training config changed in embedded metadata.
  Learned-state fingerprint changed, so internal CatBoost parameters likely changed even if tree count/depth did not.

CatBoost Deep Diff
------------------
Decoded Structure:
  Trees with split changes: 400 / 400
  Split positions changed: 2800

Leaf Values:
  Trees with leaf value changes: 400 / 400

Feature Processing:
  Float features: 13 -> 15
  Categorical features: 4 -> 2
  Total float borders: 787 -> 874
  Float feature list changes:
    Added:
      13: new_numeric_feature
      14: another_numeric_feature
  Categorical feature list changes:
    Removed:
      2: old_category_feature
      3: another_category_feature
  Feature type changes:
    9: month_feature: categorical -> float
  Float features with changed borders:
    0: numeric_feature_a: borders 59 -> 62, changed positions 59
    1: numeric_feature_b: borders 95 -> 93, changed positions 93

Create a safe synthetic CatBoost model for local testing:

python3 -m pip install catboost
python3 examples/make_sample_catboost.py
cargo run -- inspect examples/sample_catboost.cbm

The generated .cbm uses synthetic data only and is ignored by git.

Explain a single CatBoost artifact:

cargo run -- explain path/to/model.cbm

This describes the artifact as a tree ensemble, shows evidence from CBM metadata, estimates tree traversal cost, prints training metadata, and explains how to use diff --deep for exact version-to-version changes.

When Python CatBoost is available, explain also prints feature processing:

Feature Processing:
  Float features: 15
  Categorical features: 2
  Total float borders: 874
  Float feature list:
    0: numeric_feature_a (62 borders)
  Categorical feature list:
    1: category_feature_a

ONNX

Inspect an ONNX graph:

cargo run -- inspect path/to/model.onnx
cargo run -- inspect --json path/to/model.onnx
cargo run -- onnx --limit 10 path/to/model.onnx
cargo run -- onnx --json path/to/model.onnx

Example output:

ONNX Model Summary
------------------
Model: model.onnx
Format: ONNX ModelProto
File size: 197.1 KiB
  IR version: 10
  Producer: pytorch
  Graph: main_graph
  Opsets: ai.onnx=18

Graph:
  Nodes: 89
  Initializers: 33
  Value info entries: 120
  Parameters: 58646 values / 231.2 KiB

Operator Mix:
  Add                      22
  MatMul                   16
  Reshape                  12
  Transpose                10
  LayerNormalization       5

Estimated Cost:
  Estimated ops: 3226
  Most expensive:
    node_MatMul_98           MatMul       256 ops

Diff two ONNX graphs:

cargo run -- diff path/to/old_model.onnx path/to/new_model.onnx
cargo run -- diff --json path/to/old_model.onnx path/to/new_model.onnx

ONNX diff includes a Parameter Tensors section. It compares initializer tensor count, names, shapes, dtypes, and raw-data fingerprints when raw tensor bytes are stored in the ONNX file.

Explain likely ONNX architecture:

cargo run -- explain path/to/model.onnx

Example output:

ONNX Architecture Explanation
-----------------------------
Model: model.onnx

This model appears to be a transformer.

Evidence:
  - 12 Softmax nodes
  - 24 MatMul nodes
  - 24 LayerNormalization operators
  - initializer names mention embeddings

Estimated Architecture:
  Encoder/attention layers: ~12
  Hidden size: ~768
  Parameters: 109482240 values
  Estimated ops: 12345678

Why:
  Transformers repeatedly use MatMul for projections and attention scores.
  Softmax usually appears where attention scores become probabilities.
  LayerNormalization is common around transformer attention/MLP blocks.

mod-trace performs static ONNX graph inspection. It does not execute ONNX models and does not require ONNX Runtime.

Exporting A Small ONNX Model

If you have a Hugging Face model locally, export it with fixed input shapes for better cost estimates:

python3 -m pip install torch transformers onnx onnxscript
import torch
from transformers import AutoModel, AutoTokenizer

model_dir = "models/tiny-distilbert-base-cased"
onnx_path = f"{model_dir}/model_fixed.onnx"

tokenizer = AutoTokenizer.from_pretrained(model_dir)
model = AutoModel.from_pretrained(model_dir)
model.eval()

inputs = tokenizer(
    "hello mod-trace",
    return_tensors="pt",
    padding="max_length",
    max_length=8,
    truncation=True,
)

torch.onnx.export(
    model,
    (inputs["input_ids"], inputs["attention_mask"]),
    onnx_path,
    input_names=["input_ids", "attention_mask"],
    output_names=["last_hidden_state", "pooler_output"],
    opset_version=18,
    dynamo=True,
)

print(onnx_path)

Then inspect it:

cargo run -- inspect models/tiny-distilbert-base-cased/model_fixed.onnx

Fixed shapes such as [1, 8] produce better numeric estimates than symbolic shapes such as [batch, sequence].

PyTorch

mod-trace reads PyTorch torch.save files (.pt, .pth, .bin, .ckpt) natively — no torch, no Python:

mod-trace inspect      model.pt
mod-trace diff         old.pt new.pt
mod-trace explain-diff old.pt new.pt
mod-trace check --max-parameter-growth 30% old.pt new.pt

It parses the torch.save zip (pickled structure + raw tensor storages) and reports file size, tensor count, estimated parameter count (storage bytes ÷ dtype), dominant dtype, recovered parameter/layer names, and a sampled weight fingerprint that changes on a retrain/finetune. ZIP64 is handled for models over 4 GB.

Limits, by design:

  • Tensor shapes are not decoded (that needs a full pickle interpreter) — you get counts, names, and dtype, not per-tensor shapes.
  • Legacy (pre-1.6) pickle .pt and older Hugging Face pytorch_model.bin recover names + a fingerprint but not parameter sizes (tensors aren't stored as zip entries there). Modern zip-format saves get the full report.
  • .safetensors is a different format and is not read by mod-trace.

Exporting any PyTorch model to ONNX

For richer graph-level detail (operators, attention layers), or if you only have a .safetensors/legacy file, export to ONNX — the usual serving format — which mod-trace reads fully. For a plain nn.Module the export is a single call:

import torch

model.eval()
dummy = torch.randn(1, n_features)          # one example input with the right shape
torch.onnx.export(
    model,
    dummy,
    "model.onnx",
    input_names=["input"],
    output_names=["output"],
    opset_version=18,
    dynamo=True,                            # optional; the modern exporter
)
mod-trace inspect model.onnx
mod-trace diff old_model.onnx new_model.onnx

Use fixed input shapes (e.g. torch.randn(1, n_features)) rather than dynamic axes for better cost estimates. Once exported, ONNX is read natively — no PyTorch, ONNX Runtime, or other framework is needed to inspect it.

Tensor Lab

A secondary lab for explaining transformer internals on small handcrafted JSON plans (trace, compare, why, validate, quiz, demo). It is not the primary product surface. See docs/tensor-lab.md.

What It Does Not Do

mod-trace does not:

  • run inference
  • train models
  • load PyTorch directly
  • require CatBoost, PyTorch, or ONNX Runtime for inspection
  • provide GPU kernels
  • replace framework-native debugging tools

Privacy

Do not commit real model weights or private business artifacts. The repository ignores:

  • models/
  • examples/*.onnx
  • examples/*.cbm

Use examples/make_sample_catboost.py when you need a shareable .cbm demo.

Architecture

mod-trace has three small inspection paths:

  • CatBoost .cbm metadata scanner
  • ONNX protobuf graph scanner
  • JSON tensor-plan analyzer

See docs/ARCHITECTURE.md for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mod_trace-0.4.3.tar.gz (171.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

mod_trace-0.4.3-py3-none-win_amd64.whl (585.5 kB view details)

Uploaded Python 3Windows x86-64

mod_trace-0.4.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (644.7 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

mod_trace-0.4.3-py3-none-macosx_11_0_arm64.whl (582.1 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file mod_trace-0.4.3.tar.gz.

File metadata

  • Download URL: mod_trace-0.4.3.tar.gz
  • Upload date:
  • Size: 171.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mod_trace-0.4.3.tar.gz
Algorithm Hash digest
SHA256 2c39a57d50d42eb1f3eb2fc500213c8bed830271ee4d07cb4f703813ba65e8bc
MD5 04f8c1fc327a1a9859ecf8b9453aac7c
BLAKE2b-256 ba823ed701d18da20e4e502c6b72b8e4ba1593a83ad57ae40e01e48c6791dbb5

See more details on using hashes here.

Provenance

The following attestation bundles were made for mod_trace-0.4.3.tar.gz:

Publisher: release.yml on kraftaa/modellens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mod_trace-0.4.3-py3-none-win_amd64.whl.

File metadata

  • Download URL: mod_trace-0.4.3-py3-none-win_amd64.whl
  • Upload date:
  • Size: 585.5 kB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mod_trace-0.4.3-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 a4624166955dcec91822c04edce36fe56972d257d68e123b8f9ea7ad99347f62
MD5 d4cedbfeb2bf07d9ac77c8ca02b5c5a6
BLAKE2b-256 11525dc87b893062f67e77f939a5eb9638673b4541fd6c36d4855aaf4e1ad5d6

See more details on using hashes here.

Provenance

The following attestation bundles were made for mod_trace-0.4.3-py3-none-win_amd64.whl:

Publisher: release.yml on kraftaa/modellens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mod_trace-0.4.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for mod_trace-0.4.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 f6a8cfc09f7abc21da00cd8fc7f4e8f002fbeec9cdc1d40f253ca4430492152e
MD5 dbc8c7aec024ddc279abdb34652f8261
BLAKE2b-256 ed591143ee51a979d7ef0d64e64b9bea9a29789c157b017138006c4a3c6675e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for mod_trace-0.4.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on kraftaa/modellens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mod_trace-0.4.3-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for mod_trace-0.4.3-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c01a6caed052839be8ea8d336a7a31201f1d1a3463c7c40d6680a66f29618cd7
MD5 c7dd6cdbb29ab583c15e9befa068cb8b
BLAKE2b-256 09ff1549a08a7994654c61cabc8657039b8da63a11bfb468988366a83204a4a7

See more details on using hashes here.

Provenance

The following attestation bundles were made for mod_trace-0.4.3-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on kraftaa/modellens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page