Compilation Failed. Why Is There Still a “Successful Model” in the Directory?

Make output directories, temporary artifacts, and stale files part of a clear success contract.

Posted by Bruce Lee on 2026-08-07

Series contents · Engineering and Delivery · 阅读中文版

A compilation succeeds in the morning. In the afternoon, someone changes the model and compilation fails. An automation script sees that the output file still exists and sends the morning’s model to execution. The team then studies the afternoon’s graph to explain the wrong output. The investigation started with the wrong artifact.

This is a counterexample about an output contract, not a retelling of a documented incident: a file’s existence does not mean this invocation successfully produced it.

An output argument is a semantic promise

A command-line --output looks like a string, but it determines how the tool organizes state. If it sometimes means a directory, sometimes a final file, and sometimes only an intermediate IR location, callers cannot easily write reliable scripts.

A clear teaching interface is:

1
compile-model --input network.onnx --output result/

It promises a final model under result/. A debug option decides whether additional intermediate files remain; it does not redefine what the final model is.

The reviewed evolution consolidated scattered output options into a required directory argument. It rejected model-file paths accidentally supplied as directories and recognized removed options well enough to provide migration guidance. That is easier to diagnose than silently ignoring an old argument.

What should normal and debug modes share?

Normal mode usually aims for a tidy directory. Debug mode exposes stage-by-stage IR, weights, and an expanded model directory. Their retention policies differ; their compilation semantics should remain as consistent as possible.

Artifacts fall into three categories:

Category Example Lifetime
Final deliverable Loadable model Retained by the user after success
Debugging evidence Stage IR and mapping information Retained in debug mode
Temporary work Conversion scratch files Managed by the current invocation

Trouble starts when temporary files are scattered across the working directory, or when cleanup treats deliverables and evidence as scratch. A clearer flow runs conversion in a designated workspace, preserves evidence when requested, and cleans only generated directories owned by the tool.

A fixed generated-directory name can still collide with user files. The output directory’s purpose must be documented, and concurrent jobs must not share it without isolation. A cleanup function does not make its ownership boundary correct by definition.

Why changing the working directory can lose the input

Introducing a temporary workspace commonly breaks relative input paths. From a project root, data/model.onnx resolves correctly. After changing into a temporary directory, it means somewhere else.

Resolve the identity of inputs before changing execution directories. External weights referenced by a model need their own validation too; making the main file path absolute does not automatically satisfy every loader’s relative-path rules.

The output path also needs an explicit base. Is it relative to the caller’s original directory or to the temporary directory? A user should be able to predict the destination from the command without understanding the subprocess implementation.

The reviewed changes handled paths for the model, quantization file, and output directory, while consolidating debug artifacts under that directory. They reduced drifting path meanings. They do not establish that every special-character path and external-weight combination was tested.

A success criterion is more than a log message

Exit status, file existence, parseability, numerical correctness, and performance form progressively stronger evidence:

  • Exit code zero: the program considers the workflow complete.
  • Final file exists: a file is at the expected location, but it may be old.
  • File belongs to this invocation and parses: artifact identity and structure are better established.
  • Actual execution is correct: model semantics receive further validation.
  • Performance meets expectations: a separate set of experiments is still required.

The historical changes checked that an expected final file existed. Documentation also warned that an old model could remain after failure. That limitation should be preserved rather than rewritten as “stale outputs are completely solved.”

A further design could use an isolated workspace per invocation, validate success before publishing, and record a job identifier or input digest. A temporary-file-and-rename design also needs to consider filesystem boundaries, concurrent overwrite policy, and when readers can observe the result.

Why retain an old entry point but remove most of its code?

One pipeline often accumulates several historical scripts. Copying the workflow to preserve an old command is convenient initially. Over time, it produces two slightly different option sets, cleanup policies, and collections of bugs.

A lighter compatibility wrapper warns about deprecation and delegates to one primary implementation. Fixes remain centralized, and the compatibility layer does not quietly become a second compiler driver.

The tradeoff is that argument compatibility must be defined carefully. The old entry-point name may survive while old options no longer preserve their meaning. Explicit rejection with a replacement is better than guessing intent for the sake of “compatibility,” especially when output locations and automatic cleanup are involved. A wrong guess can be more damaging than an error.

A test matrix for the output contract

These are proposed checks, not results from this task:

Case Behavior to check
Empty output directory, normal mode Produce this invocation’s final model and clean scratch according to the contract
Empty output directory, debug mode Retain stage evidence while still producing the final model
Old final model exists, then compilation fails File existence does not hide the failure
Filename passed as a directory argument Produce an early, specific diagnostic
Removed option supplied Explain migration rather than ignore it
Relative input with a temporary workspace Read the intended model and associated data
Two jobs share a name or directory Apply an explicit isolation or rejection policy
Subprocesses fail at different stages Preserve useful diagnostics rather than only a wrapper exception

Do not test only whether intermediate files exist. Compare semantic results between normal and debug modes too. Debug mode must not accidentally change optimization behavior while being described as merely retaining files.

Logging should also accommodate automation. If a user pipes output into a log file, the surrounding shell must still report compiler failure correctly. Copyable documentation should express that execution contract, not merely show the shortest successful command.

Simplicity is not a contest to minimize option count

One option that hides three meanings looks compact but is difficult to use. The goal of a clear interface is fewer implicit rules for its caller to remember.

Likewise, deleting more files does not necessarily improve reliability. Retaining crucial IR after failure may be more useful than a clean directory; excessive intermediates can confuse someone receiving a final model. Explicit modes express those different needs better than asking users to guess which files are disposable.

The evidence supports an evolution in output interfaces and artifact lifetimes, not a perfectly transactional compiler. Preserving that distinction prevents an article about reliability from creating another illusion of reliability.


Series contents · Previous · Next


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !