GPUs for your projects · crypto payment without KYC How to rent
English
Open the console
Practical guide / KERNODECK

Your notebook works. Can you rerun it without its cells?

To move from a notebook to a script, start from an empty kernel, identify the inputs and extract the computation into functions. Then give the program explicit arguments and a separate output destination. Success is verified in a new process, with an expected result; exporting the cells to a Python file is not enough to make the experiment reproducible.

7 min read · Developer guide

1. Recover what the kernel still knows

The notebook file and the kernel state do not always tell the same story. A variable may come from a deleted cell, a list may have been modified several times, and an object loaded before the last code change may remain in memory. The displayed results therefore do not prove that the current cells still produce those results in their visible order.

Keep a working copy, restart the kernel, then run the cells from top to bottom. Note the first cell that fails or changes its result. Track down the missing dependency instead of manually re-injecting a variable from an old session. The Jupyter kernel is a separate process; closing a tab is not the same as rebuilding a clean environment.

Also inventory external effects: downloading, installing a package, changing directory, reading a file that was already produced, and using an environment variable. A cell whose result looks immediate may simply be reusing an old file. Your future script must be able to tell an intentional input apart from leftover trial state.

2. Write the contract before moving the code

Pick a single task to extract. For example: read a scores file, keep the identifiers whose score reaches a threshold, and write the result. The example in this guide is educational, not executed, and uses no GPU. It serves to show the dependencies of an execution, not to announce a measurement or a tool shipped with Kernodeck.

Define the input, the parameter, and the output precisely enough to verify the transformation. Here, the threshold is inclusive: a score equal to 0.5 is kept. Identifiers must stay associated with their scores, and the input order is preserved. An existing output must not be inadvertently overwritten by a new trial.

This step prevents an ambiguous migration: if the notebook excluded scores equal to the threshold while the script keeps them, you have changed the computation. Decide explicitly whether that is a fix or a regression. Keep a case sitting exactly on the boundary, not just two values far apart.

Scroll the table to read all columns.
Educational selection contract, with no claimed execution.
ItemExample valueCriterion
Inputa: 0.4; b: 0.8; c: 0.5Three distinct identifiers, scores already validated between 0 and 1.
ParameterThreshold 0.5Greater-than-or-equal comparison.
Expected outputb, then cTwo identifiers, with no duplication or reordering.

3. Extract a function that no longer depends on a cell

Separate the transformation from the read and write operations. A computation function receives its data and its threshold, then returns the selected identifiers. It does not read a global variable named seuil, does not implicitly open a file, and does not modify the input list. This lets the notebook and the script call exactly the same computation.

In the snippet, the data is assumed to have already been validated according to the previous contract. The function is therefore not a complete file validator. This limitation is intentional: check the formats at the entry point of the program, then keep the transformation easy to understand. Adding a parameter should not require hunting down the cell that had changed a value.

The notebook can remain your exploration tool. Have it import this function rather than maintaining a second copy. After modifying the module, start from a fresh kernel to compare the two paths; a previously imported old function must not skew the verification.

Educational function — to be placed in your own module, not executed here
def retenir_identifiants(records, seuil):
    return [
        record["id"]
        for record in records
        if record["score"] >= seuil
    ]


if __name__ == "__main__":
    records = [
        {"id": "a", "score": 0.4},
        {"id": "b", "score": 0.8},
        {"id": "c", "score": 0.5},
    ]
    attendu = ["b", "c"]
    obtenu = retenir_identifiants(records, 0.5)
    if obtenu != attendu:
        raise SystemExit("Sélection inattendue")

4. Make the parameters a visible input

The script's entry point processes the arguments, validates the choices, and calls the functions. The standard argparse module describes the options and produces help output; it does not know your business rules. A float that is syntactically accepted may still be outside the allowed range. The threshold in this example therefore requires an additional check.

Be explicit about path resolution: relative to the directory from which the command is launched, or to an explicitly chosen project folder. Do not use a hidden directory change in the middle of the computation. The block below shows only argument parsing; reading the data and writing remain to be wired up in the reader's program.

Keep secrets out of these arguments. Shareable parameters describe the experiment; access to a repository or storage follows another channel. A command that is useful to a colleague must be copyable without copying a token along with it.

Instructional argument parsing — excerpt not executed
import argparse
from pathlib import Path


def lire_arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", required=True, type=Path)
    parser.add_argument("--output", required=True, type=Path)
    parser.add_argument("--seuil", required=True, type=float)
    args = parser.parse_args()
    if not 0 <= args.seuil <= 1:
        parser.error("The threshold must be between 0 and 1.")
    return args

5. Give the script an end and verifiable outputs

Place the orchestration in a main function and trigger it under the condition if __name__ == "__main__". The module can then be imported by the notebook without immediately starting the processing. Imports define the tools; the main entry point decides when to read, compute, and write.

Assign a separate folder to each run. Record the non-sensitive parameters actually used and the identity of the input, then write the results. For our selection, verify the number of identifiers, their membership in the input, and the threshold rule. A well-formed JSON file can contain the wrong identifiers; its presence alone is not enough.

Provide for an explicit failure if the input file is missing or if the destination is not usable. Avoid replacing these problems with an empty list: it could be interpreted as a valid selection. The program must distinguish no result meeting the threshold from no result because reading failed.

6. Compare across two fresh runs

Start with the three instructional lines. With threshold 0.5, expect b and c; with 0.9, expect an empty list; with 0.4, expect all three identifiers. These answers follow from the contract and are not presented as results executed here. They help spot a reversed comparison operator or a wrong order.

Then run your restarted notebook and your script in a fresh process, on the same input. Compare the useful values, not screenshots or timestamps written into the files. Add a second representative set and an invalid input. Document the expected differences, such as a more sober presentation of the outputs.

An nbconvert conversion can speed up the initial move of the cells, but its magic commands may still depend on Jupyter. Remove or replace notebook-specific instructions, unnecessary displays, and improvised installations. The export is a starting point; comparing from a fresh state decides whether the migration is complete.

7. Moving to the GPU without reintroducing hidden state

Once the CPU path is understood, connect the model loading and the backend to the same explicit structure. Keep versions, precision, input, and destination. Moving to the GPU fixes neither an inconsistent cell order nor a file produced by an old trial. Separately verify that PyTorch can actually compute on the chosen device.

If two launches produce different values, distinguish a forgotten state, a random source, and the numerical limits of the computation. A seed is not a universal promise of identity across versions and hardware. For interrupted training, use the dedicated checkpoint procedure: this guide transforms the entry point without reconstructing optimizer or generator states.

The useful output is a program you can describe in a single command, along with its prerequisites and a result check. The notebook remains free to explore and plot; it no longer bears sole responsibility for remembering how the computation must be launched.

Your questions

Should notebooks be abandoned to make a project reproducible?

No. Keep the notebook for exploration and presentation, but move the reused computation into functions or modules that are also called by the script. The check must start from a fresh kernel and explicit inputs.

Is exporting a notebook to .py enough?

No. The export moves the visible code; it does not fix the order of dependencies or the effects of already-executed cells. Magic commands may still require Jupyter. Verify the script in a new process.

Should I get byte-for-byte identical files?

Only if that criterion is relevant to your format. Compare the expected identifiers, values, and business rules first. Dates or metadata may differ without changing the computation; numerical tolerances must be explicit.