Skip to content

apr export <model>.apr --format gguf panics: 'C-07: num_layers required for GGUF export' #1865

@noahgift

Description

@noahgift

Summary

apr export on any APR file that does not carry num_layers in its metadata panics at crates/aprender-core/src/format/converter/metadata.rs:384. APR-native files where num_layers was inferred at runtime (rather than stamped into metadata) cannot round-trip to GGUF.

Reproducer

$ apr export /home/noah/models/qwen2.5-coder-1.5b-instruct-q4k.apr --format gguf -o /tmp/rt.gguf
[PMAT-252] Raw passthrough: detected Q4K in APR source. Copying blocks directly (zero loss).

thread 'main' panicked at crates/aprender-core/src/format/converter/metadata.rs:384:10:
C-07: num_layers required for GGUF export
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit=101

Evidence the model is actually valid

apr inspect --json returns 339 tensors and architecture: qwen2 but does not list num_layers or qwen2.block_count in metadata:

$ apr inspect --json /home/noah/models/qwen2.5-coder-1.5b-instruct-q4k.apr | jq '.architecture, .tensor_count, .metadata."qwen2.block_count"'
"qwen2"
339
null

Meanwhile apr trace --json enumerates 28 layers from the tensor names (blk.0.attn_q ... blk.27.ffn_down). The information is present in the file; the exporter just doesn't infer it.

Root cause

metadata.rs:382-384:

let num_layers = apr_metadata
    .num_layers
    .expect("C-07: num_layers required for GGUF export");

.expect() on an Option<usize> produces a panic (Rust exit 101), not a clean CliError. Per CLAUDE.md "unwrap() banned via .clippy.toml disallowed-methods. Use expect() or ok_or_else(|| ...)?"expect is allowed but only as a sentinel for invariants that genuinely can't be violated. Here the invariant is violated (1.5B APR file in the model registry), so the call site is wrong.

num_heads and hidden_size directly below have the same shape and will panic identically for any APR file that omits them.

Suggested fix

  1. Infer num_layers from tensor names when metadata is silent. The tensor count over blk.N.* already drives apr trace --json; reuse that logic in the GGUF exporter.
  2. Replace .expect() with ok_or_else(|| AprenderError::FormatError { ... })? so a missing field surfaces as a clean validation error (exit 4), not a panic (exit 101).
  3. Backfill num_layers/num_heads/hidden_size into the APR metadata at apr stamp time for forward-compatibility, and add a falsification test that round-trips APR → GGUF → APR for every architecture-family fixture in the registry.

Why it matters for v0.35.0

The README and SPEC-HF-PUBLISH-001 both list apr export --format gguf as part of the canonical publish pipeline. paiml/albor-370m-v1 shipped successfully because its specific stamping flow happened to populate num_layers. Users converting APR files produced by older stamp tooling, or by community workflows, will hit the panic and have no recovery path.

Severity

P1 — release-blocker for v0.35.0. Panic-on-valid-input from a load-bearing publish command. Filed alongside #1864 during v0.35.0 release dogfood.

Artifacts

  • Host: noah-Lambda-Vector
  • Model: /home/noah/models/qwen2.5-coder-1.5b-instruct-q4k.apr (339 tensors, 28 layers, qwen2)
  • Build: HEAD = 0d8d52b (release/v0.35.0 worktree)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions