Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions docs/commands/clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: "nanotune clean"
description: "Remove the cached fused model to reclaim disk space"
sidebar_order: 9
---

# nanotune clean

Remove the fused model cache at `.nanotune/models/fused/`.

`nanotune export` keeps a full-precision copy of the fused model around after export so that `nanotune export --skip-fuse` can reuse it for a different quantization without redoing the fusion step. That directory can be multiple gigabytes — `nanotune clean` deletes it to reclaim the space.

## Usage

```bash
nanotune clean
```

## Options

| Flag | Description |
|------|-------------|
| `-y, --yes` | Skip the confirmation prompt (for scripts and CI) |

## Examples

```bash
# Interactively confirm before removing the fused model cache
nanotune clean

# Remove it without prompting
nanotune clean --yes
```

## What It Does

1. Checks `.nanotune/models/fused/` for a cached fused model.
2. If found, shows its size and asks for confirmation (unless `--yes` is passed).
3. Deletes the directory and reports how much space was freed.

If there's nothing to clean, it says so and exits without changes. Your exported `.gguf` files are never touched — only the intermediate fused model is removed. The next `nanotune export` will simply re-fuse the adapter.

## See Also

- [`nanotune export`](export.md) — See "Fused Model Cache" for why this directory exists
8 changes: 7 additions & 1 deletion docs/commands/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ nanotune export
|------|-------------|
| `-q, --quantization <type>` | Quantization type: `f16`, `q8_0`, `q4_k_m`, `q4_k_s` |
| `-o, --output <name>` | Output filename |
| `--skip-fuse` | Skip adapter fusion if already fused |
| `--skip-fuse` | Skip adapter fusion — requires a `fused/` cache from a previous export |

## Examples

Expand All @@ -46,6 +46,12 @@ nanotune export --skip-fuse

Pre-built llama.cpp binaries are downloaded automatically — no compilation needed.

## Fused Model Cache

Fusing the LoRA adapter into the base model produces a full-precision copy, which is kept at `.nanotune/models/fused/` after export finishes — it's not deleted. Keeping it around lets `nanotune export --skip-fuse` reuse it to produce a different quantization without redoing the fusion step.

That directory can be multiple gigabytes. Its size is shown when export completes and in `nanotune status`. Run [`nanotune clean`](clean.md) to remove it and reclaim the space — the next export will simply re-fuse the adapter.

## Quantization Types

| Type | Description |
Expand Down
1 change: 1 addition & 0 deletions docs/commands/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ A complete reference for all Nanotune CLI commands.
|---------|-------------|
| [`nanotune init`](init.md) | Initialize a new fine-tuning project |
| [`nanotune status`](status.md) | Show current project status |
| [`nanotune clean`](clean.md) | Remove the cached fused model to reclaim disk space |
| [`nanotune data add`](data.md) | Add training examples interactively |
| [`nanotune data import`](data.md) | Import training data from a file |
| [`nanotune data list`](data.md) | View and manage training data |
Expand Down
1 change: 1 addition & 0 deletions docs/commands/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ nanotune status
- **Training data** — Number of examples loaded
- **Training progress** — Current state and last training run
- **Exports** — Available GGUF files
- **Fused model cache** — Disk space used by the retained `fused/` model, if present
- **Benchmark results** — Latest benchmark summary
27 changes: 26 additions & 1 deletion src/cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ program
'Quantization type (f16, q8_0, q4_k_m, q4_k_s)',
)
.option('-o, --output <name>', 'Output filename')
.option('--skip-fuse', 'Skip adapter fusion (if already fused)')
.option(
'--skip-fuse',
'Skip adapter fusion — requires a fused/ cache from a previous export',
)
.action(async options => {
const {ExportCommand} = await import('./commands/export.js');
render(<ExportCommand options={options} />);
Expand Down Expand Up @@ -293,4 +296,26 @@ program
render(<StatusCommand />);
});

// Clean command
program
.command('clean')
.description('Remove the cached fused model to reclaim disk space')
.option('-y, --yes', 'Skip the confirmation prompt (for scripts and CI)')
.action(async (options: {yes?: boolean}) => {
// Only require --yes when there's actually a confirmation to answer —
// "nothing to clean" and "not a project" are safe to just report.
if (!options.yes && !supportsRawMode()) {
const {configExists, getFusedModelDir, hasUsableFusedModel} =
await import('./lib/config.js');
if (configExists() && hasUsableFusedModel(getFusedModelDir())) {
console.error(interactiveRequiredMessage('clean'));
console.error('Pass --yes to clean without confirmation.');
process.exitCode = 1;
return;
}
}
const {CleanCommand} = await import('./commands/clean.js');
render(<CleanCommand options={options} />);
});

program.parse();
156 changes: 156 additions & 0 deletions src/commands/clean.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {rmSync} from 'node:fs';
import {StatusMessage} from '@inkjs/ui';
import {Box, Text, useApp} from 'ink';
import {useCallback, useEffect, useState} from 'react';
import {
ExitHint,
Header,
useAutoExit,
useKeyInput,
} from '../components/index.js';
import {
configExists,
formatFileSize,
getDirectorySize,
getFusedModelDir,
hasUsableFusedModel,
} from '../lib/config.js';

interface Props {
options: {
/** Skip the y/n confirmation — needed to run under CI or in a pipeline. */
yes?: boolean;
};
}

type Status = 'confirm' | 'cleaning' | 'nothing' | 'done' | 'error';

export function CleanCommand({options}: Props) {
const {exit} = useApp();
const hasProject = configExists();
const fusedDir = getFusedModelDir();
const fusedExists = hasProject && hasUsableFusedModel(fusedDir);

const [status, setStatus] = useState<Status>(() => {
if (!hasProject) return 'error';
if (!fusedExists) return 'nothing';
return options.yes ? 'cleaning' : 'confirm';
});
const [error, setError] = useState<string | null>(null);
const [sizeLabel] = useState<string | null>(
fusedExists ? formatFileSize(getDirectorySize(fusedDir)) : null,
);

const doClean = useCallback(() => {
try {
rmSync(fusedDir, {recursive: true, force: true});
setStatus('done');
} catch (err) {
setError(
err instanceof Error
? err.message
: 'Failed to remove fused model cache',
);
setStatus('error');
}
}, [fusedDir]);

// `--yes` (or the initial `cleaning` state it sets above) skips straight
// past the confirmation prompt.
useEffect(() => {
if (status === 'cleaning') {
doClean();
}
}, [status, doClean]);

useKeyInput(input => {
if (status === 'confirm') {
if (input.toLowerCase() === 'y') {
setStatus('cleaning');
} else if (input.toLowerCase() === 'n' || input === '\x1b') {
exit();
}
} else if (
status === 'done' ||
status === 'nothing' ||
status === 'error'
) {
exit();
}
});

useAutoExit(
status === 'done' || status === 'nothing' || status === 'error',
status === 'error',
);

if (!hasProject) {
return (
<Box flexDirection="column" padding={1}>
<Header title="Clean" />
<StatusMessage variant="error">
Not a Nanotune project. Run `nanotune init` first.
</StatusMessage>
</Box>
);
}

return (
<Box flexDirection="column" padding={1}>
<Header title="Clean" />

{status === 'confirm' && (
<Box flexDirection="column">
<Text>
Fused model cache: <Text color="cyan">{sizeLabel}</Text> at{' '}
<Text color="cyan">.nanotune/models/fused</Text>
</Text>
<Text dimColor>
This is kept to speed up repeat exports via --skip-fuse.
</Text>
<Text> </Text>
<Text>
Remove it? <Text color="green">(y/n)</Text>
</Text>
</Box>
)}

{status === 'cleaning' && <Text>Removing fused model cache...</Text>}

{status === 'nothing' && (
<Box flexDirection="column">
<StatusMessage variant="info">
Nothing to clean — no fused model cache found.
</StatusMessage>
<Text> </Text>
<ExitHint>Press any key to exit</ExitHint>
</Box>
)}

{status === 'done' && (
<Box flexDirection="column">
<StatusMessage variant="success">
Removed fused model cache
</StatusMessage>
<Text> </Text>
<Text>
Freed: <Text color="cyan">{sizeLabel}</Text>
</Text>
<Text dimColor>
The next `nanotune export` will re-fuse the adapter.
</Text>
<Text> </Text>
<ExitHint>Press any key to exit</ExitHint>
</Box>
)}

{status === 'error' && (
<Box flexDirection="column">
<StatusMessage variant="error">{error}</StatusMessage>
<Text> </Text>
<ExitHint>Press any key to exit</ExitHint>
</Box>
)}
</Box>
);
}
Loading
Loading