candle-graph 0.3.0

Static structure and dataflow analysis for candle-rs models
Documentation

candle-graph

Disclaimer: candle-graph is an independent tool. It is not official and is not affiliated with, endorsed by, or maintained by candle-rs or Hugging Face.

candle-graph reconstructs parameter structure, tensor contracts, and gradient connectivity of a Candle model crate. Its versioned, crate-wide model IR and bounded query API are designed so coding agents can answer focused questions without loading an entire source tree into context.

The analyzer combines conservative Rust-source analysis with the Cargo configuration selected for the scan. An optional runtime trace can refine static tensor facts and audit gradients. It never loads checkpoint tensor payloads or requires a GPU for static analysis.

  • candle-graph/model/1: one unified IR for components, architecture edges, functions, modules, parameters, tensor contracts, stages, artifacts, optimizers, Cargo context, findings, and runtime evidence;
  • candle-graph/query/1: deterministic, bounded agent queries with fully qualified selectors, compact-by-default progressive disclosure, and limit/offset pagination;
  • candle-graph/runtime/1: JSON/JSONL tensor observations and per-parameter gradient facts; see docs/runtime-analysis-guide.md for the full workflow and v2 (time series) / v3 (profiler) extensions;
  • candle-graph/viewer/1: standalone interactive HTML visualizer (requires the visualizer feature).

See docs/features.md for the static / runtime / visualizer split: static IR for agents, runtime profiler timings, HTML for humans.

Status

Primary crate-wide UX is the Clippy-like Cargo subcommand cargo candle-graph (check / report / query / audit / view) over the conservative analyzer. It is an explicit cargo command, not a rustc lint pass and not a silent cargo build hook.

The agent-analysis path supports:

  • automatic component discovery from public API boundaries and VarBuilder/VarBuilderArgs constructors, with --root for private model roots;
  • public tensor-boundary and Candle-trait entrypoint candidates with fully qualified identities;
  • symbolic tensor rank/shape, dtype, device, layout, and requires_grad contracts;
  • Cargo package, target, feature, Candle-version, active cfg, and per-function cfg-activity facts;
  • audited Candle 0.11.0 operation rules that distinguish similarly named APIs such as RmsNorm::forward and RmsNorm::forward_diff;
  • optional runtime tensor refinement and missing/zero/non-finite gradient findings;
  • checkpoint-header verification and model-IR CI baselines.

Analysis is deliberately conservative. Unsupported expressions remain Unknown; a same-dtype operation with one known and one unknown operand is a dtype-risk, while two known differing dtypes are a dtype-conflict. Call-order architecture, pipeline stages, artifacts, and optimizer membership require --heuristic-architecture or the future compiler frontend. Struct-field composition edges are always available via --query composition with Heuristic confidence. In particular, names such as run_pipeline, train_*, save, and all_vars, filename suffixes, and source order do not create facts without that flag.

The compiler-backed design needed to derive those relationships without annotations or loading model weights is documented in docs/compiler-evidence-design.md.

Usage

Install once so Cargo discovers the Clippy-like subcommand (cargo-candle-graphcargo candle-graph):

cargo install --path . --features all --locked

Check a model crate the way you would run cargo clippy — an explicit cargo command, not a silent side-effect of cargo build. Diagnostics go to stderr; the full candle-graph/model/1 document goes to stdout (or --output):

cargo candle-graph check /path/to/model
cargo candle-graph check /path/to/model --features cuda --message-format json
cargo candle-graph report /path/to/model --output model-ir.json
cargo candle-graph query summary --path /path/to/model

check / --strict exit non-zero on proven Error findings only. Coverage-gap warnings and Information notes such as compiler-semantic-evidence stay visible but do not fail the gate. Commit a crate-wide fingerprint with --update-baseline / --check on that same command.

Numeric domain: the analyzer expands audited candle-nn bodies (e.g. 0.11.0 binary_cross_entropy_with_logit) into the same float-range pass used for local sigmoid?.log()? compositions, then labels whether the hazard can NaN a loss / poison gradients or risk inference outputs. An affine(mul>0, add>0) epsilon guard discharges StrictlyPositive and must not false-positive.

The candle-graph binary mirrors the same model-mode engine for scripts:

cargo run --release --features all -- /path/to/model --format json

Ask focused questions. Listings are compact (counts/IDs, drill_down hints, no tensor evidence); narrow with --select / singular kinds when you need contracts or evidence:

cargo candle-graph query summary --path /path/to/model
cargo candle-graph query doctor --path /path/to/model
cargo candle-graph query components --path /path/to/model
cargo candle-graph query composition --path /path/to/model --format json
cargo candle-graph query assembly --select model --path /path/to/model --format json
cargo candle-graph query pipeline --path /path/to/model --format json
cargo candle-graph query entrypoints --path /path/to/model --format json
cargo candle-graph query tensors --select 'MyModel::forward' \
  --path /path/to/model --limit 20 --offset 0 --format json
cargo candle-graph query tensor --select '<tensor-stable-id>' \
  --path /path/to/model --format json
cargo candle-graph query cargo --path /path/to/model --format json
cargo candle-graph query findings --path /path/to/model --format json
cargo candle-graph query graph-train --path /path/to/model --format json
cargo candle-graph query graph-infer --path /path/to/model --format json

Use fully qualified selectors when bare names collide:

cargo candle-graph query function --select 'model::MyModel::forward' \
  --path /path/to/model --format json

Select Cargo features/target exactly as the analyzed build does:

cargo candle-graph query cargo \
  --path /path/to/model \
  --features cuda,flash-attn \
  --target x86_64-unknown-linux-gnu \
  --format json

The analyzer follows the selected Cargo crate root and reachable mod declarations. It defaults to the library target, then the first ordinary binary; use --cargo-target <name> for another binary/example/test target. The --target option selects a Rust target triple.

Import an instrumented small-run trace:

cargo candle-graph query runtime \
  --path /path/to/model \
  --runtime-trace run.runtime.jsonl \
  --format json

Open the interactive multi-view HTML visualizer (requires candle-graph built with the visualizer or all feature):

cargo candle-graph view --path /path/to/model --output model.html

--features on this command selects Cargo features on the model crate (e.g. cuda), not candle-graph. Names like static, visualizer, runtime, and all are candle-graph build flags and are ignored when passed here. See docs/features.md and docs/visualizer.md.

Write a multi-file audit bundle (summary, doctor, findings, checkpoint verification, …):

cargo candle-graph audit runs/analyzer \
  --path /path/to/model \
  --checkpoint /path/to/model.safetensors \
  --runtime-trace runs/train/runtime.json \
  --verify-root vb \
  --strict

See docs/agent-query-api.md for the query map, docs/visualizer.md for the HTML visualizer, docs/runtime-protocol.md for trace field definitions, and docs/runtime-analysis-guide.md for the recommended runtime workflow (gradient audit, phase graphs, offline profiling).

Checkpoint verification reads only the safetensors length prefix and JSON header. A template such as model.layers.{index}.self_attn.q_proj.weight matches all concrete layer indices.

Create and check a canonical CI baseline:

cargo candle-graph check /path/to/model \
  --update-baseline model.candle-graph-baseline

cargo candle-graph check /path/to/model \
  --check model.candle-graph-baseline \
  --strict

--strict rejects proven dtype conflicts, numeric-domain violations, and other proven error findings. Coverage-gap warnings and heuristic architecture notes stay visible but do not fail the gate by themselves.

Run cargo candle-graph --help for all options.

Supported source patterns

The analyzer intentionally understands a restricted, explicit Rust dialect:

  • vb.pp(...), push_prefix, set_prefix, root, and metadata-preserving builder methods;
  • audited candle-nn 0.11.0 constructors and raw vb.get* calls;
  • crate-local inherent constructors and free helper functions taking VarBuilder;
  • multiple and optional builders, including then, map, and and_then;
  • struct literals, if/match branches, for loops, and iterator map;
  • literal and format! prefixes, including mixed segments such as block_{index}.
  • ordinary tensor calls, local bindings, branches, symbolic loop iterations, and crate-local calls;
  • dtype-preserving, explicit-cast, and same-dtype candle operations;
  • version-gated Candle operation semantics. Rules not audited for the resolved Candle version stay unknown instead of silently borrowing another version's behavior.

Unsupported or ambiguous constructs produce diagnostics instead of silently inventing a result. Unknown custom macros, non-literal tensor names, unresolved builders, cycles, and analysis limits are therefore visible in every JSON report.

Output semantics

Parameter keys are namespaced by their originating constructor argument (root). Two identical keys under different roots are different tensors. Certainty is one of:

  • certain: always registered on the analyzed path;
  • conditional: gated by a branch, optional field/builder, or constructor configuration;
  • unknown: reserved for constructs whose existence cannot be modeled safely.

Every model fact carries a stable identity and source/evidence where applicable. Function identity is based on its fully qualified name and active cfg predicates rather than absolute paths or line numbers, so ordinary source edits do not invalidate runtime correlation. Coverage counts and the compact doctor query let consumers assess an analysis before trusting individual records. dead_params(), severing_edges(), dtype_conflicts(), dtype_risks(), and paths_to() remain available through the library API.

Development

cargo fmt -- --check
cargo test --all-targets
cargo clippy --all-targets -- -D warnings

The integration suite covers the unified IR/query/runtime schemas, Cargo feature/cfg discovery, qualified symbol collisions, tensor contracts, optimizer membership, versioned Candle semantics, formatted layer families, multiple builder roots, interprocedural calls, BF16/F32 joins, no-backward severing, deterministic baselines, the CLI, HTML safety, and safetensors headers.