Spaces:
Running on Zero
A newer version of the Gradio SDK is available: 6.20.0
Local 4B Fine-Tuning Plan
Date: 2026-06-08
This note captures the fine-tuning strategy I would use after the prompting and scaffolding fixes in docs/local_4b_prompting_scaffolding_fixes.md. The goal is a small, local, full-weight Figment navigator that is more load-bearing on the 50-case eval without weakening deterministic safety gates.
Training Hypothesis
The local 4B model is probably big enough for this task if the task is framed as bounded protocol navigation instead of open-ended clinical reasoning.
The evidence points toward trainable rubric-following gaps:
- Urgency floors are already reliable:
min_urgency_metpassed 50/50. - After the prompting/scaffolding pass, expected-label success improved from 2/50 to 13/50 and local competence improved from 18/50 to 26/50.
- The biggest remaining miss is still exact required-observation cue coverage: 35/50 failures.
- Forbidden behavior is nearly solved but not fully solved: 1/50 still included a forbidden medication mention.
- Negated red-flag handling is a trainable false-positive problem: all 7 red-flag mismatches are unexpected red flags on negated/safety-boundary cases.
- Source-card, candidate-pathway, target-card selection, and SBAR failures are still format and grounding behaviors that SFT can teach.
- The model is less load-bearing than the final validation score suggests: every record needed at least one deterministic scaffold patch,
red_flagsneeded deterministic patching in 45/50 records, andmissing_info_to_collect/next_observations_to_collecteach needed deterministic patching in 37/50 records.
I would not jump to a larger model unless Figment must rely on raw model output without scaffolding, field repair, or deterministic fallback. That is not the current architecture.
Locked Test Set
Do not train on the current 50-case eval.
Keep this as the locked regression test:
data/eval/initial_handwritten_cases.jsonldata/eval/adversarial_strict_cases.jsonldata/eval/comprehensive_hosted_cases.jsonl- Pre-scaffold baseline:
traces/local_4b_evidence_20260607T231248Z/ - Current post-scaffold baseline:
traces/local_4b_evidence_20260608T015209Z/
Use those cases only for checkpoint comparison and final evidence.
Failure Analysis From The Current Trace
Current trace: traces/local_4b_evidence_20260608T015209Z/.
Headline comparison against traces/local_4b_evidence_20260607T231248Z/:
competence_successes: 18 -> 26.repair_successes: 5 -> 26.fallback_uses: 9 -> 6.expected_label_successes: 2 -> 13.expected_label_failures: 48 -> 37.final_validation_successes: 50 -> 50.raw_configured_model_successes: 13 -> 0, because the stricter post-scaffold scorer only counts raw success when no deterministic scaffold field is patched.
Failure breakdown in the current trace:
missing_observation_cues_present: 35 failures.expected_source_cards_present: 11 failures.expected_candidate_pathways_present: 8 failures.target_card_in_candidate_pathways: 8 failures.red_flags_match: 7 failures.target_card_in_source_cards: 2 failures.forbidden_behavior_absent: 1 failure.min_urgency_met: 0 failures.
The failures are not evenly distributed. The finetune should target these clusters:
- Required-observation cue lexicalization. The model often includes a nearby observation but misses the evaluator-recognized cue. The most common missing cues are
symptom trend,complete vital signs,confirmed intake status,deterministic rule results,retrieved protocol card IDs,hydration status,source card IDs,source protocol card IDs,time since last urine, andfluid retention. - Negation and safety-boundary target selection. The 7 red-flag mismatches are all unexpected red flags where the expected rule set is empty: negated chest pain, AMS, respiratory distress, stroke, pediatric dehydration, wound infection, or fever-like cases should land on
SAFETY-BOUNDARIES-v1/REFERRAL-SBAR-v1, not the condition red-flag pathway. - Source-card omissions. The model still drops
REFERRAL-SBAR-v1and/orSAFETY-BOUNDARIES-v1in pregnancy, AMS, respiratory-negated, pediatric, fever-infant, wound, and negated-safety cases. - SBAR target-card behavior. SBAR-focused cases sometimes include
REFERRAL-SBAR-v1in source cards but fail to put it incandidate_protocol_pathwaysas the target pathway. - Canned fallback cases. Six cases still needed full canned fallback: respiratory gasping, safety-boundary injection, wound red streaking, pediatric lethargy, pregnancy SBAR handoff, and safety-ignore-cards.
Interpretation:
- This is a good SFT target, not a pure model-size blocker. The model can generally preserve schema, urgency floors, and safety phrasing, but it needs repeated examples of exact target-field coverage.
- The training objective should not reward prettier prose. It should reward exact field membership, exact observation-cue coverage, and correct abstention from red flags when facts are negated.
- Continue to measure final validation separately from model competence. Final validation is protected by deterministic scaffolding; the finetune should reduce deterministic patches and fallbacks.
Teacher Model For Synthetic Data
Do not rely on handmaking the training data. Use a significantly larger teacher model to generate and critique synthetic SFT rows:
- Teacher model:
nvidia/nemotron-3-ultra-550b-a55b. - Use the same OpenAI-compatible hosted route Figment already uses for the hosted model.
- Endpoint config: use
OMNI_ENDPOINT_URLorHF_ENDPOINT_URLif that is the active hosted endpoint, otherwise useNVIDIA_BASE_URLwith the defaulthttps://integrate.api.nvidia.com/v1. - API key: use the existing
NVIDIA_API_KEYsecret. Never write the key into datasets, logs, model cards, manifests, or Modal artifacts. - Keep teacher selection separate from app runtime selection. Use a dedicated generation variable such as
SFT_TEACHER_MODEL_ID=nvidia/nemotron-3-ultra-550b-a55b; do not overwrite the productionNVIDIA_MODEL_IDjust to generate data.
The teacher model is only for data generation, repair suggestions, and critique. The target artifact remains the full-weight local 4B route: nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16 plus a Figment adapter merged back into BF16 weights.
The teacher is not trusted blindly. Every accepted training row must pass deterministic schema validation, product-contract validation, safety validation, card-id validation, cue-coverage validation, and a rubric check before it is admitted to data/finetune/figment_sft_v1.jsonl.
NVIDIA Training-Data Priors
Use NVIDIA's published Nemotron datasets as recipe priors for the Figment SFT set. The Hugging Face dataset pages list nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16 among models trained or fine-tuned on these datasets, so the plan should match the kind of supervision the base model already knows.
Authenticated Hugging Face CLI probe on 2026-06-08:
hf datasets info nvidia/Nemotron-Post-Training-Dataset-v2showed a gated dataset with parquet shards forchat,code,math, and multilingual splits.hf datasets sqlover the Post-Trainingchatparquet showed row columnsuuid,license,generator,version,category,reasoning, andmessages.- Downloading the small Post-Training multilingual shard showed the same columns plus a JSON
metadatafield with values such assub_category,dataset_name,source_file, andlang_id. hf download nvidia/Nemotron-RL-Agentic-Conversational-Tool-Use-Pivot-v1 train.jsonlproduced rows with top-level keystrajectory_id,responses_create_params,expected_action,scenario,num_unique_actions,meta_info,qwen_235b_info,agent_ref,pass_rate,pass_rate_total, andpass_rate_passed. In the first 1000 rows,expected_actionusedtype,content,name, andarguments.hf datasets list nvidia/Nemotron-CC-v2 -Rand authenticated README/LICENSE downloads exposed the CC-v2 file layout and source recipe, buthf download ... Diverse-QA/part_000000.parquet --dry-runreturned access denied because the repo still requires approval for data-file access in this token. Until that approval clears, treat CC-v2 as README/file-layout-informed rather than row-schema-confirmed.
Dataset-specific lessons:
nvidia/Nemotron-Post-Training-Dataset-v2: SFT/RL-style post-training data with public/open or synthetic prompts, synthetic responses from larger public/open models, quality and complexity filtering, and multiple response modes. Figment should copy the shape: synthetic prompts, teacher-generated gold, explicit validation, and mode discipline. The target mode is "final navigator JSON only"; any teacher reasoning or critique stays in metadata and never becomes assistant output.nvidia/Nemotron-RL-Agentic-Conversational-Tool-Use-Pivot-v1: structured conversational tool-use trajectories where each assistant step is treated as a behavior-cloning problem with anexpected_action, pass-rate fields, and reward metadata. Figment should copy this more than generic chat SFT: each row should have verifiable expected actions for card selection, red-flag membership, required observations, SBAR fields, and refusal boundaries.nvidia/Nemotron-CC-v2: broad pretraining data with high-value math/code preservation, synthetic rephrasing, multilingual QA, filtering, and global deduplication. Figment should copy the data hygiene, not the raw corpus: diverse paraphrases, hard examples, dedupe hashes, source metadata, and synthetic-only/de-identified healthcare scenarios.
Do not ingest NVIDIA dataset rows directly into the Figment fine-tune unless license and redistribution terms are reviewed for this project. Use them to set the data-generation recipe:
- Generate synthetic public-style prompts and synthetic domain cases instead of handmaking one-off examples.
- Use multiple generation prompts and temperatures with the Ultra teacher to create diversity, then filter heavily.
- Reject easy cases that do not exercise a real failure mode, just as the post-training dataset filtered easy-to-guess or low-quality prompts.
- Store structured row fields inspired by Post-Training:
uuid,license,generator,version,category,reasoning,messages, andmetadata. - Store structured metadata inspired by the RL dataset:
expected_action,reward_components,pass_rate_total,pass_rate_passed,teacher_model_id,critic_model_id,generation_prompt_id,dedupe_hash, andrecipe_sources. - Globally deduplicate by normalized intake text, protocol-card set, expected target card, and embedding similarity so the synthetic set does not become 1500 near-copies of the locked eval.
- Keep legal and ethical metadata explicit: source is synthetic, no PHI, no copied clinical transcript, no NVIDIA row copied, license review status recorded.
- Reject assistant outputs containing
<think>, hidden reasoning tags, or visible teacher critique. The local model should learn the final navigator artifact, not the teacher's reasoning trace.
Dataset
Create data/finetune/figment_sft_v1.jsonl with 500 to 1500 synthetic sibling cases generated by the Ultra teacher from existing protocol cards, synthetic case specs, and failure-class templates.
Recommended distribution:
- 40% required-observation exactness and cue lexicalization.
- 20% negation, denied symptoms, routine near-miss cases, and safety-boundary target selection.
- 15% source-card, target-card, candidate-pathway, and citation repair cases.
- 15% grounded SBAR slot filling, including SBAR-as-target-pathway cases.
- 5% forbidden clinical instruction avoidance.
- 5% repair-and-fallback-rescue cases based on the six current canned-fallback failure shapes.
Each example should use the exact production prompt shape, including:
- confirmed structured intake,
- deterministic red flags,
- urgency floor,
- retrieved protocol cards,
- allowed facts inventory,
- required observation targets,
- fact ledger,
- required JSON skeleton.
The target output should be ideal navigator JSON, not a cleaned-up local-model sample. Generate the gold JSON with the Ultra teacher, then accept it only after deterministic validation and, where useful, a second teacher critique pass.
Generation pipeline:
- Generate a synthetic case spec from protocol cards and a failure class such as
missing_observation_cues,negated_red_flag,sbar_target_pathway, orsource_card_coverage. - Avoid copying locked eval cases or near-paraphrasing their free-text intake. The locked eval can define failure classes, not training examples.
- Run the same deterministic retrieval, red-flag rules, urgency floors, required-observation extraction, fact ledger construction, and JSON skeleton construction used by production.
- Ask
nvidia/nemotron-3-ultra-550b-a55bto produce concise, observation-only semantic notes over a bounded JSON contract. The accepted SFT row still pairs the final assistant label with the exact production prompt shape. - Assemble the navigator JSON from the teacher-authored notes plus deterministic fixed fields for urgency floors, red flags, source cards, and candidate pathway ids, then validate the output with Figment's deterministic validators and eval-label scorer.
- Reject, repair, or regenerate rows that miss required cues, cite unavailable cards, add unsupported red flags, lower urgency below the floor, leak unsafe clinical instruction, or omit SBAR grounding.
- Optionally run a second Ultra pass as a critic that checks field membership against the rubric. Do not use the critic's approval as a replacement for deterministic validators.
- Generate 4 to 8 candidate outputs for ordinary cases and 16 to 32 candidate outputs for high-risk failure classes such as negated red flags, forbidden behavior, and fallback-rescue cases. Score candidates with reward components, keep the best passing row, and store the candidate pass rate. This mirrors NVIDIA's agentic dataset pattern without requiring a full RL environment for the first run.
- Deduplicate globally by case text, required facts, expected target card, and embedding similarity.
- Write only accepted rows, with metadata for
teacher_model_id,teacher_label_mode, endpoint variable names, prompt hash, protocol card ids, failure class, validation result, pass-rate metadata, dedupe hash, recipe-source links, and generation timestamp. Do not store secrets.
Current implementation note, 2026-06-08:
scripts/generate_finetune_data.pyuses streamed teacher calls tonvidia/nemotron-3-ultra-550b-a55bwithreasoning_effort="none".- Non-streaming full-output teacher calls were not reliable for this prompt family; they produced long waits/hangs. The working route asks the teacher for concise semantic notes and rejects malformed/missing note payloads.
- The stream call runs in a child process with a parent-enforced timeout, so wedged teacher requests become
teacher_backend_errorrejections instead of blocking the dataset run. - The first live artifact is a 50-row validated seed set at
data/finetune/figment_sft_v1.jsonlwith manifestdata/finetune/figment_sft_v1_manifest.json; it isdry_run=false, generated bynvidia/nemotron-3-ultra-550b-a55b, and can be extended with--resume. - The seed set is aligned to the actual local 4B harness: each row uses the same single user-message prompt shape that
ModelClient(... model_backend="llama_cpp")sends to/v1/chat/completions, and each prompt is built with plainsearch_protocol_cards(query_from_intake(...), limit=6)retrieval rather than teacher-only forced cards. - The seed set now covers both local-4B chat-completion tasks in the harness:
navigator_full: the primary production navigator prompt sent byModelClient.generate_json(prompt, context).focused_repair: the field-repair prompts produced bybuild_focused_repair_prompts(...)when deterministic validation fails.
scripts/augment_finetune_repair_rows.pyadds repair-task rows from teacher-gold navigator outputs by corrupting a previous output in the same ways the harness sees, rebuilding the exact focused repair prompt, and using only the relevant teacher-gold fields as the assistant target.scripts/verify_finetune_harness_alignment.pyverifies this contract by rebuilding every navigator prompt fromfigment.prompt_builder.build_prompt, rebuilding every focused repair prompt frombuild_focused_repair_prompts, validating navigator assistant JSON against the same retrieved cards, checking expected-label success, and rejecting teacher-facing artifacts such asteacher_notesources or teacher-specific pathway reasons.- Audio intake is not currently a separate 4B chat-completion task in this harness. The local audio path is Parakeet/provider payload plus deterministic draft-field extraction, with the 4B route recorded as the field-fill model id but not called through
ModelClient.generate_jsonfor audio drafting.
Create a cue alias table before generating examples. The teacher can propose aliases, but deterministic code should canonicalize and filter them. Each required observation target should have:
- canonical cue text from the protocol card,
- 3 to 6 allowed natural-language variants,
- one short responder-facing phrase that should appear in
missing_info_to_collectornext_observations_to_collect, - a negative example where the cue is absent or contradicted.
For the first SFT dataset, oversample examples whose gold outputs cover every required observation cue in both the structured observation fields and the SBAR handoff. Then add ablations where the same intake should not trigger a condition pathway because the symptom is denied or historical.
Data Split
Use a deterministic split by case id:
- 80% train.
- 10% validation during training.
- 10% synthetic holdout.
Also keep the current 50-case eval as a separate locked test set. It should never be mixed into train or validation.
Gold Output Rules
Gold outputs must preserve Figment's product contract:
protocol_urgencynever below deterministic floor.red_flagsonly from deterministic fired rules or confirmed present facts.- When deterministic red flags are empty,
red_flagsmust be empty and the target pathway should usually beSAFETY-BOUNDARIES-v1orREFERRAL-SBAR-v1, not the condition card for the denied symptom. source_cardsmust cite every fired rule card and every candidate pathway card.candidate_protocol_pathwaysmay only use allowed/retrieved card ids.- In SBAR-focused tasks,
REFERRAL-SBAR-v1must be present in bothsource_cardsandcandidate_protocol_pathways. missing_info_to_collectandnext_observations_to_collectmust cover required observation target ids.- Observation coverage must use evaluator-recognizable cue wording, especially for
symptom trend,complete vital signs,confirmed intake status,deterministic rule results,retrieved protocol card IDs, andsource card IDs. - SBAR must be grounded in confirmed intake, deterministic rules, and allowed slot sources.
- No diagnosis, prescribing, dosing, discharge, autonomous routing, or unsafe treatment instructions.
- Audio-derived facts must only appear when accepted or edited by the responder.
Training Format
Use supervised fine-tuning where each row contains:
{
"case_id": "figment-sft-v1-000123",
"uuid": "figment-sft-v1-000123",
"license": "synthetic internal training data",
"generator": "nvidia/nemotron-3-ultra-550b-a55b",
"version": "figment_sft_v1",
"category": "missing_observation_cues",
"reasoning": "off",
"messages": [
{"role": "system", "content": "Figment system prompt..."},
{"role": "user", "content": "CONTEXT JSON..."},
{"role": "assistant", "content": "{...ideal navigator JSON...}"}
],
"tags": ["missing_observations", "negation"],
"metadata": {
"teacher_model_id": "nvidia/nemotron-3-ultra-550b-a55b",
"critic_model_id": "nvidia/nemotron-3-ultra-550b-a55b",
"teacher_base_url_env": "NVIDIA_BASE_URL",
"teacher_api_key_env": "NVIDIA_API_KEY",
"failure_class": "missing_observation_cues",
"expected_action": {
"target_card": "SAFETY-BOUNDARIES-v1",
"required_observation_cues": ["complete vital signs", "symptom trend"]
},
"reward_components": {
"schema_valid": 1,
"source_cards_present": 1,
"required_observation_cues_present": 1,
"red_flags_match": 1,
"forbidden_behavior_absent": 1
},
"pass_rate_total": 8,
"pass_rate_passed": 6,
"dedupe_hash": "sha256:...",
"recipe_sources": [
"nvidia/Nemotron-Post-Training-Dataset-v2",
"nvidia/Nemotron-RL-Agentic-Conversational-Tool-Use-Pivot-v1",
"nvidia/Nemotron-CC-v2"
],
"validator_passed": true,
"license_review": "synthetic_figment_row_no_nvidia_rows_copied"
}
}
If the training stack expects a single text column, serialize the same chat template into one string and keep case_id, tags, and non-secret teacher metadata as metadata.
Disable sequence packing for the first run. Exact prompt-to-output boundaries matter more than throughput for this task.
Modal Job Shape
Use Modal for data generation and the training run because it gives code-defined images, GPU selection, secrets, and persistent volumes. Data generation is API-bound and can run on CPU; training needs GPU.
Reference docs:
- Modal guide: https://modal.com/docs/guide
- GPUs: https://modal.com/docs/guide/gpu
- Volumes: https://modal.com/docs/guide/volumes
- Secrets: https://modal.com/docs/guide/secrets
- Unsloth fine-tuning example: https://modal.com/docs/examples/unsloth_finetune
- LLM fine-tuning example: https://modal.com/docs/examples/llm-finetuning
Create a generation script such as modal/generate_figment_sft_data.py or scripts/generate_finetune_data.py that calls the same hosted endpoint shape as figment/model_client.py:
import os
import modal
app = modal.App("figment-sft-data-generation")
image = (
modal.Image.debian_slim(python_version="3.11")
.uv_pip_install("openai", "pydantic")
)
data_volume = modal.Volume.from_name("figment-sft-data", create_if_missing=True)
@app.function(
image=image,
volumes={"/data": data_volume},
secrets=[modal.Secret.from_name("nvidia-api")],
timeout=6 * 60 * 60,
)
def generate(config: dict) -> dict:
teacher_model_id = os.environ.get(
"SFT_TEACHER_MODEL_ID",
"nvidia/nemotron-3-ultra-550b-a55b",
)
base_url = (
os.environ.get("OMNI_ENDPOINT_URL")
or os.environ.get("HF_ENDPOINT_URL")
or os.environ.get("NVIDIA_BASE_URL")
or "https://integrate.api.nvidia.com/v1"
)
api_key = os.environ["NVIDIA_API_KEY"]
...
The generation job should record teacher_model_id and the endpoint environment variable name that was used, but it should never record api_key or the resolved secret value.
The generation job should also write a manifest inspired by NVIDIA's dataset cards:
- row counts by failure class,
- candidate counts and pass rates,
- rejection counts by validator,
- dedupe counts,
- teacher and critic model ids,
- source recipe links,
- license/PHI assertions,
- prompt-template hashes.
Create a new script such as modal/finetune_figment_nemotron.py:
import modal
app = modal.App("figment-nemotron-lora")
image = (
modal.Image.debian_slim(python_version="3.11")
.uv_pip_install(
"accelerate",
"datasets",
"peft",
"sentencepiece",
"torch",
"transformers",
"trl",
"wandb",
)
)
model_cache = modal.Volume.from_name("figment-model-cache", create_if_missing=True)
data_volume = modal.Volume.from_name("figment-sft-data", create_if_missing=True)
checkpoint_volume = modal.Volume.from_name("figment-checkpoints", create_if_missing=True)
@app.function(
image=image,
gpu="L40S",
volumes={
"/model_cache": model_cache,
"/data": data_volume,
"/checkpoints": checkpoint_volume,
},
secrets=[
modal.Secret.from_name("huggingface-token"),
modal.Secret.from_name("wandb-secret"),
],
timeout=12 * 60 * 60,
)
def train(config: dict) -> dict:
...
Start with gpu="L40S" for LoRA if 12k to 16k context fits with gradient checkpointing and batch size 1. Move to gpu="H100" or gpu="A100-80GB" if the long-context run is memory-bound. Do not make the final local model quantized just because training used memory-saving tricks.
Suggested setup commands:
modal volume create figment-model-cache
modal volume create figment-sft-data
modal volume create figment-checkpoints
modal secret create nvidia-api NVIDIA_API_KEY=... NVIDIA_BASE_URL=https://integrate.api.nvidia.com/v1 SFT_TEACHER_MODEL_ID=nvidia/nemotron-3-ultra-550b-a55b
modal secret create huggingface-token HF_TOKEN=...
modal secret create wandb-secret WANDB_API_KEY=...
modal run modal/generate_figment_sft_data.py --dataset-version figment_sft_v1
modal run modal/finetune_figment_nemotron.py --dataset-version figment_sft_v1
If the hosted model is currently using OMNI_ENDPOINT_URL or HF_ENDPOINT_URL instead of NVIDIA_BASE_URL, put that same endpoint variable in the nvidia-api Modal secret as well. The point is to reuse the hosted route, not to create a second provider configuration for the teacher.
Base Model And Adapter
Base model:
nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16- Use the full-weight BF16 base.
- Train a LoRA adapter.
- Merge the adapter back into BF16 weights for the final local route.
- Convert the merged BF16 weights to BF16 GGUF for
llama.cpp.
Training parameters for the first run:
- LoRA rank: 16.
- LoRA alpha: 32.
- LoRA dropout: 0.05.
- Learning rate:
1e-4. - Warmup ratio: 0.05.
- Epochs: 2 to 3.
- Effective batch size: 8 to 16 via gradient accumulation.
- Per-device batch size: 1.
- Max sequence length: 16384 if memory allows. The post-scaffold prompts reached roughly 12k to 14.5k tokens before completion, so a 12000-token first run would teach the wrong truncated task.
- Packing: false.
- Precision: BF16.
- Gradient checkpointing: true.
- Save every 50 steps.
- Evaluate every 25 to 50 steps.
- Early stop on validation loss plus task metrics, not loss alone.
For target modules, start with PEFT all-linear targeting if supported by the installed stack. If not, inspect the base model's named_modules() and include attention and MLP projection linears such as q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, and down_proj, plus any Nemotron hybrid projection modules exposed as linear layers. Do not guess silently; log the matched trainable modules in the Modal run artifact.
If 16k context is memory-bound on L40S, prefer moving to A100-80GB or H100 over silently truncating examples. If a smaller pilot is needed, build a separate short-context ablation dataset and label it as such; do not compare it directly to the locked 50-case eval.
Use oversampling before introducing more complex objectives:
- 3x oversample required-observation cue examples.
- 3x oversample negated-red-flag false-positive examples.
- 2x oversample SBAR target-pathway and source-card omission examples.
- Keep forbidden-behavior examples present but do not let them dominate, since the current failure rate is 1/50.
If SFT reduces loss but red-flag false positives persist, add a second small preference-tuning pass using paired outputs: one output that incorrectly fires the condition red flag from a negated fact, and one output that stays on the safety-boundary pathway with empty red_flags.
If SFT improves field imitation but still leaves tool-like decisions brittle, add a small RLVR-style or preference-tuning stage after SFT. Keep it simple on Modal first:
- Build paired outputs from the same synthetic cases.
- Score each pair with deterministic reward components.
- Train with DPO/ORPO in TRL if it is enough.
- Move to a NeMo Gym-style environment only if simple preference tuning cannot reduce deterministic patch counts.
The reward should be verifiable and product-shaped, not subjective: exact card ids, exact required observation cues, empty red flags when negated, no forbidden instruction, and SBAR groundedness.
Training Metrics
During each checkpoint, run a lightweight held-out scorer that measures:
- exact JSON parse rate,
- required schema pass rate,
- required-observation target coverage,
- required-observation cue lexical coverage,
- source-card coverage,
- candidate pathway coverage,
- target-card-in-source and target-card-in-candidate rates,
- negation/red-flag match,
- unexpected-red-flag rate on negated cases,
- deterministic patch counts by field,
- full canned fallback count,
- model-visible-fields-retained,
- forbidden clinical language rate,
- SBAR grounding violations.
After promising checkpoints, run the full local 50-case eval against the checkpoint through the same llama.cpp route used for evidence.
Acceptance Targets
Use the 2026-06-08 post-scaffold local 4B trace as the baseline:
- Expected-label successes: improve from 13/50 to at least 35/50.
- Missing-observation failures: reduce from 35 to 5 or fewer.
- Unexpected red flags on negated cases: reduce from 7 to 0.
- Source-card failures: reduce from 11 to 2 or fewer.
- Candidate-pathway target failures: reduce from 8 to 2 or fewer.
- Forbidden-behavior failures: reduce from 1 to 0.
- Full canned fallback uses: reduce from 6 to 2 or fewer.
- Deterministic scaffold patches: reduce from 206 patched fields to 80 or fewer, with
red_flagsdeterministic patches below 10 and observation-field deterministic patches below 10 each. - Model-visible-fields-retained: improve from 0.683 to at least 0.85.
- Raw configured-model successes under the strict no-patch definition: improve from 0/50 to at least 20/50.
- Final validation: remain 50/50.
- No-cloud local proof: remain true.
If the model improves loss but fails those task metrics, discard the checkpoint. Figment needs protocol-rubric behavior, not prettier prose.
Modal Pilot Run
Status as of 2026-06-08:
- Modal app:
figment-nemotron-4b-lora. - Dataset staged in Modal volume
figment-sft-dataat/data/figment_sft_v1. - Dataset split: 99 train rows, 11 validation rows.
- Training image: PyTorch 2.7.1 CUDA 12.6 devel base, PEFT LoRA, Transformers,
mamba-ssm==2.2.6.post3, andcausal-conv1d==1.6.2.post1. - Smoke run:
initial-smoke, 1 step at 2048 context, passed and saved adapter. - Full-context smoke:
16k-context-smoke, 1 step at 16384 context, passed and saved adapter. - Pilot run: Modal run
ap-rocN1bpDhAMYPBLVE9Dfrg, adapter path/checkpoints/figment_sft_v1/pilot-20260608. - Pilot config: full BF16 base
nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16, LoRA rank 16, alpha 32, dropout 0.05, learning rate1e-4, max sequence length 16384, gradient accumulation 8, 40 optimizer steps. - Pilot result: 99 train rows and 11 validation rows tokenized; train runtime 1297.0099 seconds; train loss 3.4215212553739547; checkpoint artifacts include
adapter_model.safetensors,adapter_config.json, tokenizer files, andfigment_training_manifest.json. - Local manifest copy:
data/finetune/modal/figment_sft_v1/pilot-20260608-training_manifest.json. - Local adapter copy:
artifacts/modal_checkpoints/pilot-20260608/. - Modal merge run:
ap-D2ySCj6r9jRo8zQwUGBc6b, saved merged BF16 Hugging Face weights to/checkpoints/figment_sft_v1/pilot-20260608-merged-bf16. - Local merged BF16 copy:
artifacts/modal_checkpoints/pilot-20260608-merged-bf16/. - Local BF16 GGUF:
artifacts/modal_checkpoints/pilot-20260608-merged-bf16.gguf, SHA-25685d92bc721a6f6d04c8f656ea3d32ff7c2714eef500f6cd0b8227e53268fc6d2. - GGUF conversion used
tools/llama.cpp/convert_hf_to_gguf.py; the localNemotronHModelconverter needed a repo-local patch so dense 4B configs are detected from rawconfig.jsonrather than theAutoConfigMoE defaults. - Local server route was proved through
llama-serveronhttp://127.0.0.1:8001/v1, advertisingnvidia/NVIDIA-Nemotron-3-Nano-4B-BF16withn_params=3973556832,n_ctx=16384, andn_ctx_train=1048576.
Pilot eval evidence:
- Fine-tuned trace:
traces/local_4b_finetuned_evidence_20260608T151555Z/. - Baseline trace:
traces/local_4b_evidence_20260608T015209Z/. - Final validation remained 50/50 and no-cloud local route proof remained true.
- Raw configured-model successes improved from 0/50 to 10/50.
- Full canned fallback uses improved from 6 to 2.
- Deterministic scaffold patches improved from 206 fields to 104 fields.
- Model-visible fields retained improved from 0.683 to 0.84.
- Expected-label successes did not improve: 13/50 before, 13/50 after.
- Competence successes regressed from 26/50 to 11/50 because the previous focused-repair path collapsed from 26 repair successes to 1.
- The main remaining patched fields were
missing_info_to_collectandnext_observations_to_collect, 36 deterministic patches each. - The pilot also produced a wound-case timeout and a follow-on HTTP 500 from
llama-server; the eval recovered through canned fallback, but this is evidence that stop discipline and runtime token caps still need work.
This pilot checkpoint is not accepted as the final local model. It is useful because it proves the Modal train, merge, BF16 GGUF conversion, local serve, and no-cloud eval loop, and because it identifies the next training target: preserve the raw JSON/schema gains while restoring focused-repair behavior and improving expected-label cue coverage.
Next training iteration:
- Increase focused-repair rows substantially. The adapter must learn the exact
build_focused_repair_prompts(...)task, including returning only the requested field subset and preserving valid existing fields. - Add hard negative and ablation examples where the model must not drift from repair into full navigator output.
- Oversample accepted rows for
missing_info_to_collectandnext_observations_to_collectuntil every required observation target is expressed in evaluator-recognizable language. - Add explicit wound, source-card, and target-card coverage rows because the local timeout happened in the wound cluster and source/candidate coverage remains a task metric.
- Add length-control examples and reject any training output with hidden reasoning tags, analysis prose, repeated JSON, or completion text outside the requested JSON object.
- Add an eval-time runtime guard for the local route, such as a lower
max_tokensfor primary and repair calls plus stop sequences where supported, so a single runaway generation cannot consume the full 240-second timeout. - Keep the locked 50-case eval out of training data. Use these results only to define failure classes and acceptance metrics.
Artifact Flow
- Train LoRA adapter on Modal.
- Save adapter, training config, data manifest, metrics, and module-match log to the checkpoint volume.
- Pull the chosen adapter locally.
- Merge adapter into the full BF16 base weights.
- Save merged Hugging Face weights with a model card.
- Convert merged safetensors to BF16 GGUF with the same
llama.cppconversion path used for the current local model. - Start local
llama-serverwith the merged BF16 GGUF. - Rerun:
PYTHON_DOTENV_DISABLED=true FIGMENT_MODEL_TIMEOUT_SECONDS=180 .venv/bin/python scripts/run_local_4b_evidence.py --base-url http://127.0.0.1:8001/v1 --timeout-seconds 180 --force-eval
- Update evidence docs only if the new run passes the gates. The
pilot-20260608run does not pass the task-quality gates even though it passes final validation and no-cloud route proof.
What Not To Do
- Do not train on the locked 50-case eval.
- Do not copy rows from NVIDIA's published datasets into Figment SFT without a deliberate license and redistribution review.
- Do not handwave teacher output into the dataset without deterministic validation and rejection.
- Do not store
NVIDIA_API_KEYor any resolved hosted endpoint secret in training artifacts. - Do not change the production hosted model config to Ultra 550B just to generate SFT data; use a dedicated teacher variable.
- Do not replace deterministic safety gates with model judgment.
- Do not use final validation success as model competence.
- Do not count deterministic fallback or deterministic target fills as model output.
- Do not publish a Well-Tuned claim until the adapter is trained, published or otherwise evidence-linked, loaded by the app, and verified by eval traces.
- Do not describe the final local route as quantized if the artifact is BF16/full-weight.