You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/SKILL.md
+8-9Lines changed: 8 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,7 +113,7 @@ Training never runs inside the `nemo` CLI process. After `submit`, the platform'
113
113
- Resolve the CLI per **Pre-flight — CLI resolution** before any `nemo …` command; run from the **nemo-platform** git root, not a plugin subfolder.
114
114
- Set `NEMO_BASE_URL` (or `NMP_BASE_URL`) only when the user gives a platform URL; default `http://127.0.0.1:8080` (same as `http://localhost:8080`). Track whether the user **overrode** the base URL — see **Platform unreachable** below.
115
115
-**Platform unreachable** — if any platform API call fails with a connection error (`Connection error`, timeout, refused):
116
-
-**User gave a custom URL** (e.g. `10.0.0.51:8080`) or you exported a non-default `NEMO_BASE_URL` / `NMP_BASE_URL`: stop and tell the user the platform is not reachable at that address. Do **not** offer to start local services.
116
+
-**User gave a custom URL** (e.g. `$NMP_BASE_URL`) or you exported a non-default `NMP_BASE_URL` / `NEMO_BASE_URL`: stop and tell the user the platform is not reachable at that address. Do **not** offer to start local services.
117
117
-**Default URL only** (no user override): **ask** whether to start the platform locally. If they agree, from the **nemo-platform** git root run in the **background**:
118
118
119
119
```bash
@@ -139,7 +139,7 @@ Training never runs inside the `nemo` CLI process. After `submit`, the platform'
139
139
- **Do not use local`docker info`** to pick automodel vs unsloth. Run `nemo jobs list-execution-profiles -f json` against the user's platform (login first only if auth is enabled — see **Authentication**; see `references/troubleshooting.md`). Default output is a table — **`-f json` is required** for scripting; parse **stdout only** (do not pipe `2>&1` into `json.load`).
140
140
- **Do not merge stderr into stdout when parsing JSON** — `submit`, `explain`, and `-f json` commands write **JSON on stdout**; harmless warnings like `Configuration file not found, using defaults` go to **stderr**. Piping with **`2>&1`** before `json.load` raises `JSONDecodeError` even when submit **succeeded** — a common cause of **duplicate jobs** when the agent re-submits after a parse error. Parse stdout only; redirect stderr if needed (`2>/dev/null`). See `references/troubleshooting.md` § **Parsing CLI JSON**.
141
141
- For submit/image/plugin errors (both backends), read `references/troubleshooting.md`. Unsloth needs the `nmp-unsloth-training` container image on the **platform host's** Docker daemon (see `docker/unsloth/README.md`).
142
-
- **Missing training image on a remote platform** — if the user gave a non-localhost `NEMO_BASE_URL` / `NMP_BASE_URL` (e.g. `10.0.0.51:8080`) and the job errors with `Failed to pull image`, `manifest unknown`, or missing `nmp-unsloth-training` / automodel training image: **do not** run `docker build`, `docker pull`, or `docker buildx bake` on the agent machine. Report with **Report to user** (use **Output adapter fileset (planned):** on error), then append on-target build steps from `references/troubleshooting.md` § **Missing training images**.
142
+
- **Missing training image on a remote platform** — if the user gave a non-localhost `NMP_BASE_URL` / `NEMO_BASE_URL` and the job errors with `Failed to pull image`, `manifest unknown`, or missing `nmp-unsloth-training` / automodel training image: **do not** run `docker build`, `docker pull`, or `docker buildx bake` on the agent machine. Report with **Report to user** (use **Output adapter fileset (planned):** on error), then append on-target build steps from `references/troubleshooting.md` § **Missing training images**.
143
143
- **Gated HuggingFace models** (Llama, Gemma, …) — confirm `hf-token` + fileset `token_secret` before submit; download fails with `Failed to access upstream storage` / 502 when missing. See **HuggingFace token (gated models)** and `references/troubleshooting.md` § **Gated HuggingFace models**.
144
144
- **Post-training eval format** — use the same CHAT `messages` JSONL as training. **Do not** flatten rows to `prompt`/`expected`for the evaluator. Send `messages[:-1]` at inference (exclude final assistant label); score against `messages[-1].content`. See `references/post-training-eval.md` and `references/eval_helpers.py`.
145
145
- **LoRA adapters load automatically for eval** — when a job completes, the adapter is registered on the model entity and hot-reloaded on any **READY** deployment with `lora_enabled: true`. **Do not** update deployments or providers before eval. **Do** route LoRA eval through the **provider** gateway (`/provider/<name>/-/v1` with `model: default--<adapter>`); the model-entity path (`/model/<entity>/-/v1`) always hits the base model. See `references/post-training-eval.md` § **Request routing (base vs LoRA)**.
@@ -600,8 +600,8 @@ The adapter `<output.name>` is registered on `default/<model-entity>`. Weights a
600
600
601
601
| Target | Gateway path | OpenAI base URL | Request `"model"` field |
**Common mistake:** posting to the model-entity URL with `"model": "default--<output.name>"` still runs the **base** model. Base-vs-adapter eval will look identical until LoRA requests use the **provider** URL above. See `references/post-training-eval.md` § **Request routing (base vs LoRA)**.
607
607
@@ -619,7 +619,7 @@ Match training context at inference — send **`messages[:-1]`** (all turns exce
619
619
#### Example — LoRA adapter via provider
620
620
621
621
\`\`\`bash
622
-
export NEMO_BASE_URL=<platform-url> # omit when using default localhost
622
+
export NMP_BASE_URL=<platform-url> # omit when using default localhost; NEMO_BASE_URL also works
623
623
nemo inference gateway provider post v1/chat/completions <provider> --workspace default \\
#### Example — base model via model-entity (comparison)
634
634
635
635
\`\`\`bash
636
-
export NEMO_BASE_URL=<platform-url>
636
+
export NMP_BASE_URL=<platform-url>
637
637
nemo inference gateway model post v1/chat/completions <model-entity> --workspace default \\
638
638
--body '{
639
639
"model": "default/<model-entity>",
@@ -651,18 +651,17 @@ Validation loss from training is **not** accuracy. To compare base vs adapter on
651
651
\`\`\`bash
652
652
cd /path/to/nemo-platform
653
653
uv run python plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/references/eval_helpers.py \\
654
-
--base-url <platform-url> \\
655
654
--model-entity <model-entity> \\
656
655
--adapter <output.name> \\
657
656
--provider <provider> \\
658
657
--dataset-fileset <dataset-fileset> \\
659
658
--split validation.jsonl
660
659
\`\`\`
661
660
662
-
Uses CHAT `messages` rows unchanged from the training fileset (`messages[:-1]` at inference). Repeat `--adapter` for multi-adapter compare. `--provider` is optional when a READY provider is auto-discovered.
661
+
Uses CHAT `messages` rows unchanged from the training fileset (`messages[:-1]` at inference). Repeat `--adapter` for multi-adapter compare. `--provider` is optional when a READY provider is auto-discovered. Set `NMP_BASE_URL` (or pass `--base-url`) when the platform is not localhost — the helper reads `$NMP_BASE_URL` / `$NEMO_BASE_URL` by default.
663
662
```
664
663
665
-
Use the user's platform URL in`NEMO_BASE_URL` when they overrode it; omit the export line fordefault `http://127.0.0.1:8080`. Substitute `<provider>`, `<NEMO_BASE_URL>`, and entity/adapter names with values from discovery — do not leave generic placeholdersin the user-facing report. Do **not** tell the user to update the deployment or add the adapter to a provider before calling it — registration on the model entity is sufficient.
664
+
Use the user's platform URL in`NMP_BASE_URL` when they overrode it; omit the export line fordefault `http://127.0.0.1:8080`. Substitute `<provider>`, concrete URLs, and entity/adapter names with values from discovery — do not leave generic placeholdersin the user-facing report. Do **not** tell the user to update the deployment or add the adapter to a provider before calling it — registration on the model entity is sufficient.
666
665
667
666
**Save report to `/tmp`** — unless the user opts out, write the full Markdown report (header, **Training configuration**, **Using the adapter** when `completed`, and **Resources created** when a slug or new filesets were used) to `/tmp/fine-tune-result-<slug-or-job-suffix>.md`. Use the random slug from the run when one was assigned; otherwise use the job id suffix (e.g. `a925b07ff678`).
{"role": "user", "content": "<user turn or multi-turn prompt>"},
13
+
{"role": "assistant", "content": "<label to predict>"}
14
14
]
15
15
}
16
16
```
@@ -23,7 +23,7 @@ Multi-turn rows use the same rule: the **final** `messages[-1]` turn is the assi
23
23
| Send **`messages[:-1]`** at inference (exclude only the final assistant label) | Pass full `messages` including the label turn, or use `{"messages": "{{ item.messages }}"}` unfiltered |
24
24
| Score against **`messages[-1].content`** (final assistant turn) | Score against a renamed `expected` field unless you also keep `messages`|
25
25
26
-
Single-turn MCQA (user + assistant) is the degenerate case: `messages[:-1]` is just the user turn.
26
+
Single-turn rows (one user prompt + one assistant label) are the degenerate case: `messages[:-1]` is just the user turn.
27
27
28
28
Automodel and unsloth both train on this shape when `has_chat` is true (see `hf-conversion.md`, `dataset-formats.md`).
`eval_helpers.py` auto-discovers a READY provider that serves the base entity (or pass `--provider <name>`). Adapter weights still hot-reload on the deployment — no provider update per adapter.
74
76
@@ -83,37 +85,40 @@ Optional sanity checks:
83
85
84
86
If base and LoRA scores were identical (~99% same outputs), the adapter was almost certainly called through the **model-entity** path. That path always resolves to the base VirtualModel — the `"model": "default--<adapter>"` field in the body is ignored. Fix: route LoRA through the **provider** URL with the same `model` field. `eval_helpers.build_platform_model_target()` and the CLI implement this split automatically.
For commonsense_qa-style MCQA, treat **`normalized_accuracy`** as the primary metric (`normalize_mcqa_answer` strips `A. foo` / markdown).
90
+
For multiple-choice or short-label SFT, treat **`normalized_accuracy`** as the primary metric when labels need normalization (`normalize_mcqa_answer` strips `A. foo`, markdown, etc.).
89
91
90
92
| Observation | Likely meaning |
91
93
|-------------|----------------|
92
-
| Base & LoRA both ~59% normalized, within ~1 pp | LoRA hit **model-entity** path (base only) — check `warnings` and gateway logs |
93
-
| Base raw exact 0%, normalized ~59% | Normal for base on MCQA (formatted prose answers)|
94
-
| LoRA normalized >> base (e.g. 76% vs 59%)| Correct provider routing and real adapter lift |
95
-
| Train loss dropped sharply but eval flat | Wrong eval routing or need more epochs — val loss ≠ accuracy |
94
+
| Base & LoRA normalized scores match within ~1–2 pp | LoRA likely hit **model-entity** path (base only) — check `warnings` and gateway logs |
95
+
| Base raw exact match low, normalized much higher | Normal when the base model emits formatted prose but normalized labels match|
96
+
| LoRA normalized clearly above base| Correct provider routing and real adapter lift |
97
+
| Train loss dropped sharply but eval flat | Wrong eval routing, mismatched inference settings, or need more epochs — val loss ≠ accuracy |
96
98
97
99
### Epoch / adapter ablations
98
100
99
101
Resolve adapter names from completed job specs instead of guessing:
100
102
101
103
```python
104
+
import os
102
105
from eval_helpers import list_completed_job_adapters, compare_adapters, build_eval_payload
103
106
107
+
base_url = os.environ.get("NMP_BASE_URL") or os.environ.get("NEMO_BASE_URL") or"http://127.0.0.1:8080"
108
+
104
109
jobs = list_completed_job_adapters(
105
-
base_url="http://10.0.0.51:8080",
110
+
base_url=base_url,
106
111
workspace="default",
107
-
model_entity="qwen3-1.7b",
108
-
dataset_fileset="commonsense_qa",
112
+
model_entity="<model-entity>",
113
+
dataset_fileset="<dataset-fileset>",
109
114
)
110
115
# jobs[0].epochs, jobs[0].adapter_name, jobs[0].backend — sorted newest first
0 commit comments