# Actor SFT Dataset V0 This directory contains the first-pass dataset tooling for fine-tuning an AI Puppet Theater Actor agent. It prepares chat-style supervised fine-tuning rows for the target model `openbmb/MiniCPM5-1B`; it does not train, quantize, publish, or integrate a model into the Space. The goal is to teach the Actor agent to return one compact, valid JSON object for a single puppet beat. The output shape matches the planned actor contract: ```json { "intent": "inspect_prop", "line": "This rubber duck squeaks exactly like a guilty witness.", "emotion": "investigative", "gesture": "leans toward the glowing prop", "stage_effect": "prop_table_glow", "memory_update": "Noted that the rubber duck behaved like evidence.", "tool_request": { "tool": "inspect_prop", "args": {"prop": "rubber duck"}, "reason": "The prop may reveal a stage clue." } } ``` `memory_update` may be `null` when no useful memory should be saved. `tool_request` may also be `null`. Allowed tools are `inspect_prop`, `consult_stage_oracle`, and `change_lighting`. ## Schema Each SFT row is JSONL with: ```json { "id": "actor-sft-v0-000001", "source_mix": ["synthetic_v0", "deterministic_templates", "ai_puppet_theater_runtime_schema"], "row_type": "prop_inspection", "messages": [ {"role": "system", "content": "You are an Actor agent in AI Puppet Theater..."}, {"role": "user", "content": "premise: ...\nshow_state JSON: ...\nactor JSON: ...\ndirector_instruction: ..."}, {"role": "assistant", "content": "{\"intent\":\"...\",\"line\":\"...\"}"} ] } ``` The assistant message content is a serialized JSON object, not a nested object, so it is directly usable as a chat SFT completion. Rows created from optional local seed files also include: ```json { "source_dataset": "G-reen/TheatreLM-v2.1-Characters", "transformation": "seeded_synthetic_actor_json" } ``` The raw external text is used only to seed premise, persona, setting, memory, prop, and Director-instruction material. Assistant completions are still generated by this repo's validated Actor JSON templates. Row types in v0: - `normal_reaction` - `prop_inspection` - `oracle_consult` - `lighting_change` - `memory_callback` - `secret_hint_or_reveal` - `finale` - `comedic_confusion` ## Generate Run from the repository root: ```bash python finetune/scripts/generate_actor_sft_v0.py ``` Generation is deterministic by default using a fixed seed, so the sample and eval files can be regenerated reproducibly. The synthetic-v0 pool currently uses 40 premises, 25 actor profiles, 30+ props, 10+ moods, 10+ stage/lighting states, multiple line templates per row type, multiple memory update templates, and multiple Director instructions per row type. Optional external seed material can be provided as local JSONL files: - `finetune/external_seeds/theatrelm_sample.jsonl` - `finetune/external_seeds/rpgpt_sample.jsonl` These files are optional. If they are absent, generation prints a short message and continues with synthetic-only data. To use another local path: ```bash python finetune/scripts/generate_actor_sft_v0.py \ --theatrelm-seed-path /path/to/theatrelm_sample.jsonl \ --rpgpt-seed-path /path/to/rpgpt_sample.jsonl ``` The supported seed sources are: - [G-reen/TheatreLM-v2.1-Characters](https://huggingface.co/datasets/G-reen/TheatreLM-v2.1-Characters) - [practical-dreamer/RPGPT_PublicDomain-alpaca](https://huggingface.co/datasets/practical-dreamer/RPGPT_PublicDomain-alpaca) Raw external rows are not committed. `finetune/external_seeds/` is gitignored except for `.gitkeep`. Seeded rows include source metadata and should be credited when the full generated dataset is published separately as a Hugging Face Dataset repo. External seed ingestion applies simple demo-safety filters and skips empty, malformed, very long, sexually explicit, heavily profane, or graphically violent rows. The filter is intentionally conservative and does not replace manual review before publishing. Default output: - `finetune/data/actor_sft_v0.jsonl` - `finetune/data/actor_sft_v0_train.jsonl` - `finetune/data/actor_sft_v0_val.jsonl` - `finetune/data_samples/actor_sft_v0_sample.jsonl` - `finetune/data_samples/actor_eval_prompts.jsonl` The full generated dataset under `finetune/data/` is gitignored. Keep only the small sample, eval prompt files, scripts, and docs in this repo. ### Targeted V1 Hardening Dataset V1 is a targeted synthetic hardening set for the v0 Actor LoRA failure modes seen in eval. It keeps v0 reproducible and adds a separate dataset version with more oracle, prop, lighting, and memory callback rows. Generate v1: ```bash python finetune/scripts/generate_actor_sft_v0.py --version v1 ``` Default v1 output: - `finetune/data/actor_sft_v1.jsonl` - `finetune/data/actor_sft_v1_train.jsonl` - `finetune/data/actor_sft_v1_val.jsonl` - `finetune/data_samples/actor_sft_v1_sample.jsonl` V1 defaults to 2,200 rows. Use `--rows` if you want a different size in the accepted 2,000-2,500 range: ```bash python finetune/scripts/generate_actor_sft_v0.py --version v1 --rows 2400 ``` V1 hardening examples enforce the same seven assistant top-level fields and over-sample: - clean `consult_stage_oracle` calls with only `tool`, `args.question`, and `reason` - clean `inspect_prop` calls with only `tool`, `args.prop`, and `reason` - clean `change_lighting` calls with only `tool`, `args.mood`, and `reason` - memory callbacks that include `line` and do not copy input/state fields into assistant JSON - non-finale reactions that avoid `deliver_finale` and `final_bow_lights` - single serialized JSON objects with no markdown, continuation, duplicate object, or copied state fields ## Validate Run: ```bash python finetune/scripts/validate_actor_sft.py finetune/data/actor_sft_v0.jsonl ``` Validate v1: ```bash python finetune/scripts/validate_actor_sft.py finetune/data/actor_sft_v1.jsonl ``` The validator checks row shape, chat roles, assistant JSON parsing, exact assistant fields, line length, allowed row types, tool request shape, and allowed tools. It prints total rows, valid and invalid counts, train/val counts when sibling split files are present, row type distribution, and tool distribution. For v1, validation also rejects `change_lighting` args that do not use the current app runtime key `mood`, extra tool keys, copied state/input fields such as `show_state`, `recent_transcript`, `held_props`, `latest_prop`, `latest_audience_action`, or `tool_results`, and finale intent/effects outside finale context. ## Strict Local Audit Before retraining after a regression, run the stricter local audit. It checks that assistant completions are exactly one JSON object, detects duplicate top-level keys, rejects missing or extra assistant fields, rejects copied state/input fields, validates strict tool shapes, and checks finale intent/effects against `show_state`. ```bash python finetune/scripts/audit_actor_sft.py finetune/data/actor_sft_v1.jsonl python finetune/scripts/audit_actor_sft.py finetune/data/actor_sft_v0.jsonl ``` The audit intentionally follows the current app runtime tool schema. At the time of writing, `change_lighting` expects `args: {"mood": "..."}` in `puppet_theater/tools.py`. ## Synthetic-v0 Status This is a deterministic synthetic v0 dataset. It is intentionally small, safe, and template-driven so the pipeline can be reviewed before model training. It covers no-tool responses, prop inspection, oracle consultation, lighting changes, memory callbacks, secret hints/reveals, finales, and comedic confusion. The full generated dataset is expected to be published separately as a Hugging Face Dataset repo after review. This codebase keeps only reproducible scripts plus small committed examples. If optional external seeds are used in a published dataset, credit the source datasets above. ## Modal LoRA Training The first training target is a LoRA/QLoRA adapter for `openbmb/MiniCPM5-1B`. LoRA trains a small adapter on top of the base model instead of fully retraining all model weights. The first output is an adapter directory. Later tasks can merge that adapter into the base model and convert or quantize the merged model to GGUF for llama.cpp. Training dependencies are isolated in `finetune/requirements-train.txt`; the main Gradio app does not import them. ### Setup Install and authenticate Modal locally: ```bash pip install modal modal setup ``` Generate the local train/val files before launching Modal: ```bash python finetune/scripts/generate_actor_sft_v0.py python finetune/scripts/validate_actor_sft.py finetune/data/actor_sft_v0.jsonl ``` The Modal wrapper mounts the local `finetune/` directory into the job image, including the generated `finetune/data/actor_sft_v0_train.jsonl` and `finetune/data/actor_sft_v0_val.jsonl` files. Outputs and model cache live in a persistent Modal Volume named `ai-puppet-theater-finetune`. `openbmb/MiniCPM5-1B` may be publicly downloadable. If Hugging Face access is required, create a Modal secret containing `HF_TOKEN` and opt in to using it: ```bash modal secret create huggingface-secret HF_TOKEN=hf_... MODAL_HF_SECRET_NAME=huggingface-secret modal run finetune/modal_train_actor_lora.py::smoke_test ``` Do not commit tokens or put them in dataset files. ### Smoke Test Run a tiny Modal training job that uses only 20 train rows and 10 eval rows: ```bash modal run finetune/modal_train_actor_lora.py::smoke_test ``` The smoke test defaults to an `A10` GPU. To change the GPU class: ```bash MODAL_GPU=A10G modal run finetune/modal_train_actor_lora.py::smoke_test ``` The smoke-test adapter is saved in the Modal Volume under: ```text /vol/outputs/minicpm5-actor-lora-smoke ``` ### Full Run Run against the full generated train/val split: ```bash modal run finetune/modal_train_actor_lora.py::train_full ``` Default training settings: - model: `openbmb/MiniCPM5-1B` - max sequence length: `1024` - epochs: `2` - learning rate: `2e-4` - LoRA rank/alpha/dropout: `16` / `32` / `0.05` - per-device train batch size: `2` - gradient accumulation: `8` - save/eval strategy: `epoch` - seed: `42` - QLoRA: enabled by default The full adapter is saved in the Modal Volume under: ```text /vol/outputs/minicpm5-actor-lora ``` ### V1 Hardening Run Generate and validate the targeted v1 dataset first: ```bash python finetune/scripts/generate_actor_sft_v0.py --version v1 python finetune/scripts/validate_actor_sft.py finetune/data/actor_sft_v1.jsonl ``` Train a separate v1 adapter on Modal without deleting or overwriting the v0 adapter: ```bash modal run finetune/modal_train_actor_lora.py::train_v1 ``` This uses: - train file: `finetune/data/actor_sft_v1_train.jsonl` - val file: `finetune/data/actor_sft_v1_val.jsonl` - base model: `openbmb/MiniCPM5-1B` - epochs: `2` - output dir: `/vol/outputs/minicpm5-actor-lora-v1` The downloaded/local adapter name should be: ```text finetune/outputs/minicpm5-actor-lora-v1 ``` ### Retrieve Outputs Use the Modal volume CLI to download the trained adapter directory from the persistent Volume: ```bash mkdir -p finetune cd finetune modal volume get --force ai-puppet-theater-finetune /outputs/minicpm5-actor-lora cd .. ``` This writes the Modal directory `/vol/outputs/minicpm5-actor-lora` to local `finetune/outputs/minicpm5-actor-lora`. Passing `finetune/outputs/minicpm5-actor-lora` as the local destination can fail if that directory already exists. After download, confirm the adapter files are present locally. Full runs may have adapter files at the output root and/or inside epoch checkpoints such as `checkpoint-158`: ```bash ls finetune/outputs/minicpm5-actor-lora find finetune/outputs/minicpm5-actor-lora -name adapter_config.json -o -name adapter_model.safetensors ``` The source directory in Modal is `/vol/outputs/minicpm5-actor-lora`; the local destination is `finetune/outputs/minicpm5-actor-lora`. Local adapter outputs under `finetune/outputs/` are gitignored. Retrieve the v1 adapter similarly: ```bash mkdir -p finetune cd finetune modal volume get --force ai-puppet-theater-finetune /outputs/minicpm5-actor-lora-v1 cd .. ``` This writes `/vol/outputs/minicpm5-actor-lora-v1` to local `finetune/outputs/minicpm5-actor-lora-v1`. ### Local Script The training script can also run directly in a CUDA environment with the training requirements installed: ```bash pip install -r finetune/requirements-train.txt python finetune/scripts/train_minicpm5_actor_lora.py \ --train_file finetune/data/actor_sft_v0_train.jsonl \ --val_file finetune/data/actor_sft_v0_val.jsonl \ --output_dir finetune/outputs/minicpm5-actor-lora \ --max_train_samples 20 \ --max_eval_samples 10 \ --epochs 0.05 ``` The Mac local machine is best used for repo work and later GGUF/llama.cpp testing. The intended training path is Modal/CUDA, not the Hugging Face Space runtime. ## Adapter Eval Before merging, quantizing, publishing, or integrating the adapter, run generation against the held-out Actor eval prompts and validate whether responses are clean Actor JSON. Default local eval command: ```bash python finetune/scripts/eval_minicpm5_actor_lora.py \ --adapter_dir finetune/minicpm5-actor-lora \ --eval_file finetune/data_samples/actor_eval_prompts.jsonl \ --output_file finetune/eval_outputs/minicpm5_actor_lora_eval.jsonl ``` Quick local smoke eval: ```bash python finetune/scripts/eval_minicpm5_actor_lora.py --limit 3 ``` The script loads `openbmb/MiniCPM5-1B`, applies the local LoRA adapter from `finetune/minicpm5-actor-lora`, generates short deterministic responses with `temperature=0.0` and `max_new_tokens=192` by default, validates JSON shape/tool calls/line length, prints aggregate metrics, and writes detailed generations to: ```text finetune/eval_outputs/minicpm5_actor_lora_eval.jsonl ``` Eval distinguishes JSON and schema quality levels: - **Raw clean JSON** means the model returned exactly one JSON object and no extra text. - **Extracted usable JSON** means the evaluator found and parsed the first complete balanced JSON object, even if the model continued with extra text afterwards. - **Has required fields** means the parsed object includes usable `intent`, `line`, `emotion`, `gesture`, `stage_effect`, `memory_update`, and `tool_request` fields. - **Exact top-level schema** means the parsed object has only those seven top-level fields. - **Sanitized actor JSON usable** means extra top-level fields were dropped and tool args were normalized into the runtime schema while preserving the required Actor output. Detailed eval rows keep `assistant_text`, extracted `json_text`, parsed JSON, `sanitized_actor_json`, and validation diagnostics such as `parse_error`, `clean_json_error`, `tool_error`, `missing_required_fields`, `extra_top_level_field_names`, and `forbidden_top_level_field_names`. Forbidden top-level fields include copied persona/state fields such as `speaking_style`, `show_state`, `recent_transcript`, `tool_results`, `setting`, and `story_phase`. Tool requests are validated strictly and also sanitized for demo/runtime reliability. For example, `inspect_prop` keeps only `{"prop": ...}`, `consult_stage_oracle` keeps only `{"question": ...}`, and `change_lighting` keeps the current runtime shape `{"mood": ...}`. Generated `change_lighting` aliases such as `{"lighting": ...}` or `{"color": ...}` are normalized to `{"mood": ...}` for app compatibility. Tool-name aliases such as `consult oracle` and `consult_oracle` are normalized to `consult_stage_oracle` for diagnostics. If a generated tool request has extra args or a normalizable tool name but can be reduced safely, the eval output records `sanitization_needed: true`, preserves `original_tool_request`, and writes `sanitized_tool_request`. Unparsed outputs are not counted as sanitized usable. For experiments, `--append_eval_suffix` adds a stricter reminder to return exactly one JSON object with only the seven Actor keys and not copy input fields. It is off by default because eval metrics should reflect the adapter's normal prompt behavior, and some adapters become less stable with extra instruction text. If local Mac inference is too slow or unsupported, run eval on Modal/CUDA from the repository root. This uses the local adapter directory mounted into the Modal image and writes eval details to the persistent Modal Volume: ```bash cd /Users/shubhamsetia/learn/huggingface_hackathon/AI-Puppet-Theater modal run finetune/modal_train_actor_lora.py::eval_adapter --limit 3 ``` Run the full eval by omitting `--limit`: ```bash modal run finetune/modal_train_actor_lora.py::eval_adapter ``` Evaluate the v1 adapter stored in the Modal Volume: ```bash cd /Users/shubhamsetia/learn/huggingface_hackathon/AI-Puppet-Theater modal run finetune/modal_train_actor_lora.py::eval_adapter_v1 --limit 10 ``` Run the full v1 eval by omitting `--limit`: ```bash modal run finetune/modal_train_actor_lora.py::eval_adapter_v1 ``` The Modal eval output path is: ```text /vol/eval_outputs/minicpm5_actor_lora_eval.jsonl ``` The Modal v1 eval output path is: ```text /vol/eval_outputs/minicpm5_actor_lora_v1_eval.jsonl ``` Evaluate the merged v0 model after running `merge_v0`: ```bash modal run finetune/modal_train_actor_lora.py::eval_merged_v0 --limit 10 ``` The Modal merged-model eval output path is: ```text /vol/eval_outputs/minicpm5_actor_merged_v0_eval.jsonl ``` Download it locally with: ```bash mkdir -p finetune/eval_outputs modal volume get --force ai-puppet-theater-finetune \ /eval_outputs/minicpm5_actor_lora_eval.jsonl \ finetune/eval_outputs/minicpm5_actor_lora_eval.jsonl ``` Download the v1 eval output locally with: ```bash mkdir -p finetune/eval_outputs modal volume get --force ai-puppet-theater-finetune \ /eval_outputs/minicpm5_actor_lora_v1_eval.jsonl \ finetune/eval_outputs/minicpm5_actor_lora_v1_eval.jsonl ``` Download the merged-model eval output locally with: ```bash mkdir -p finetune/eval_outputs modal volume get --force ai-puppet-theater-finetune \ /eval_outputs/minicpm5_actor_merged_v0_eval.jsonl \ finetune/eval_outputs/minicpm5_actor_merged_v0_eval.jsonl ``` Keep this as eval only: do not merge, convert to GGUF, publish, or integrate the model in this step. ## Merge LoRA Adapter The current best Actor adapter is v0: - local adapter path: `finetune/minicpm5-actor-lora/` - published adapter: `build-small-hackathon/AI-Puppet-Theater-MiniCPM5-Actor-LoRA` Merge v0 into `openbmb/MiniCPM5-1B` to create a standalone Hugging Face model folder for later GGUF conversion: ```bash python finetune/scripts/merge_actor_lora.py ``` Default merge inputs and output: - base model: `openbmb/MiniCPM5-1B` - adapter dir: `finetune/minicpm5-actor-lora` - output dir: `finetune/outputs/minicpm5-actor-merged` The script saves the merged model with `safe_serialization=True`, saves tokenizer files, and writes `merge_manifest.json`. Use `--dtype float16`, `--dtype bfloat16`, or `--dtype float32` to control merge dtype. If local memory is tight, run the Modal merge entrypoint instead. It merges the v0 adapter from `/vol/outputs/minicpm5-actor-lora` and writes the merged model to `/vol/outputs/minicpm5-actor-merged`: ```bash modal run finetune/modal_train_actor_lora.py::merge_v0 ``` This step only creates a merged Hugging Face model folder. It does not convert to GGUF, quantize, publish, or integrate the model into the Space. ## GGUF Conversion After merging the v0 Actor LoRA adapter, convert the standalone Hugging Face model folder to GGUF for later llama.cpp testing. Do not commit generated `.gguf` files; `finetune/outputs/gguf/` and `*.gguf` are gitignored. Install and build llama.cpp locally on Mac outside this repo, for example as a sibling directory: ```bash cd .. git clone https://github.com/ggml-org/llama.cpp.git cd llama.cpp cmake -B build cmake --build build --config Release -j cd ../AI-Puppet-Theater ``` If you already have llama.cpp somewhere else, set `LLAMA_CPP_DIR` when running the conversion script. Make sure the merged model is available locally at: ```text finetune/outputs/minicpm5-actor-merged ``` If it was merged on Modal, download it first: ```bash mkdir -p finetune cd finetune modal volume get --force ai-puppet-theater-finetune /outputs/minicpm5-actor-merged cd .. ``` Convert only to f16 GGUF: ```bash LLAMA_CPP_DIR=../llama.cpp \ MERGED_MODEL_DIR=finetune/outputs/minicpm5-actor-merged \ GGUF_OUT_DIR=finetune/outputs/gguf \ OUTTYPE=f16 \ PYTHON_BIN="uv run --with sentencepiece python" \ bash finetune/scripts/convert_actor_merged_to_gguf.sh convert ``` This writes: ```text finetune/outputs/gguf/minicpm5-actor-f16.gguf ``` Quantize to the default `Q4_K_M` GGUF: ```bash LLAMA_CPP_DIR=../llama.cpp \ GGUF_OUT_DIR=finetune/outputs/gguf \ OUTTYPE=f16 \ QUANT_TYPE=Q4_K_M \ bash finetune/scripts/convert_actor_merged_to_gguf.sh quantize ``` This writes: ```text finetune/outputs/gguf/minicpm5-actor-q4_k_m.gguf ``` Run conversion and quantization together: ```bash LLAMA_CPP_DIR=../llama.cpp \ PYTHON_BIN="uv run --with sentencepiece python" \ bash finetune/scripts/convert_actor_merged_to_gguf.sh all ``` For automated GGUF eval, prefer `llama-completion` with `-no-cnv`. This local llama.cpp build reports that `--no-conversation` is not supported by `llama-cli` and asks callers to use `llama-completion` for non-interactive one-shot generation. Quick llama-completion smoke test: ```bash ../llama.cpp/build/bin/llama-completion \ -m finetune/outputs/gguf/minicpm5-actor-q4_k_m.gguf \ -no-cnv \ -n 192 \ --temp 0 \ --reasoning off \ --reasoning-budget 0 \ -p 'SYSTEM: You are an Actor agent in AI Puppet Theater. Return only one valid JSON object. No markdown. No commentary. Keep the puppet line short, theatrical, and speakable. USER: premise: A moon mayor denies stealing the town last spoon show_state JSON: {"story_phase":"complication","latest_prop":"silver spoon","finale_requested":false} actor JSON: {"name":"Mina Moonbutton","mood":"curious","tools":["inspect_prop"]} director_instruction: Inspect the latest prop and keep the line short. Return exactly one JSON object with exactly these keys: intent, line, emotion, gesture, stage_effect, memory_update, tool_request. Do not omit stage_effect. ASSISTANT JSON:' ``` Run the local GGUF eval harness against the same Actor eval prompts: ```bash python finetune/scripts/eval_actor_gguf.py \ --llama_bin ../llama.cpp/build/bin/llama-completion \ --model finetune/outputs/gguf/minicpm5-actor-q4_k_m.gguf \ --limit 3 \ --prompt_format simple_json ``` Run a 10-prompt comparison across supported prompt formats: ```bash python finetune/scripts/eval_actor_gguf.py \ --llama_bin ../llama.cpp/build/bin/llama-completion \ --model finetune/outputs/gguf/minicpm5-actor-q4_k_m.gguf \ --limit 10 \ --prompt_format auto ``` Supported GGUF prompt formats are `raw`, `simple_json`, `chatml`, and `system_user_assistant`. The `auto` mode runs all of them and prints metrics per format. The eval details are written to: ```text finetune/eval_outputs/minicpm5_actor_gguf_eval.jsonl ``` The GGUF eval harness uses the same first-balanced-JSON extraction and Actor JSON sanitizer metrics as the Transformers eval. It uses one-shot command arguments, not interactive stdin. It probes `llama-completion --help` and passes `--reasoning off`, `--reasoning-budget 0`, and stop strings only when the installed binary supports those flags. For this build, the harness prefers `-no-cnv` when available and falls back to `--no-conversation` only if the binary advertises that flag. It prints progress for each row, captures stdout/stderr in the eval JSONL, and records runtime marker diagnostics. If a GGUF output uses `tool_request: []`, strict `tool_request` remains false, but the eval sanitizer may convert that empty list to `null` and count it as `sanitization_needed` for runtime usability. If outputs contain `[Start thinking]`, repeated `JSON:` prefixes, assistant continuations, `available commands:`, `chat template is available, enabling conversation mode`, `please use llama-completion instead`, or interactive prompts like `>`, treat that as a prompt/template or binary mismatch and compare the prompt formats before judging the merged model itself. `--llama_cli` remains as a backward-compatible alias for `--llama_bin`, but `llama-cli` may enter interactive mode for this model/build and should be used only for manual smoke checks. Do not publish the GGUF until llama.cpp eval is acceptable. Expected shape: ```json {"intent":"inspect_prop","line":"This silver spoon squeaks like a guilty witness.","emotion":"investigative","gesture":"leans toward the glowing prop","stage_effect":"prop_table_glow","memory_update":"Noted the silver spoon as evidence.","tool_request":{"tool":"inspect_prop","args":{"prop":"silver spoon"},"reason":"The prop may reveal one concrete stage clue."}} ``` If conversion fails due to MiniCPM5 architecture or tokenizer support, capture the exact `convert_hf_to_gguf.py` error. The likely next step is updating to the latest llama.cpp or using an alternate conversion path once MiniCPM5 support is available. ## Publishing GGUF The existing LoRA adapter publisher is separate from GGUF publishing and should not be reused for the quantized llama.cpp artifact. The LoRA publisher uploads adapter files such as `adapter_model.safetensors` and `adapter_config.json`; the GGUF path stages the standalone quantized file plus a GGUF-specific model card. Target GGUF model repo: ```text build-small-hackathon/AI-Puppet-Theater-MiniCPM5-Actor-GGUF ``` Prepare a local publish directory without uploading anything: ```bash python finetune/scripts/prepare_gguf_publish.py --clean ``` Dry-run the publish prep plan: ```bash python finetune/scripts/prepare_gguf_publish.py --dry_run ``` The prep script creates: ```text finetune/publish_gguf/ README.md minicpm5-actor-q4_k_m.gguf publish_manifest.json eval/minicpm5_actor_gguf_eval.jsonl # if the eval file exists locally ``` The script does not call Hugging Face APIs and does not run `hf upload`. When you are ready to publish manually, run: ```bash hf repo create build-small-hackathon/AI-Puppet-Theater-MiniCPM5-Actor-GGUF --type model --public hf upload build-small-hackathon/AI-Puppet-Theater-MiniCPM5-Actor-GGUF finetune/publish_gguf . --repo-type model --commit-message "Add Q4_K_M GGUF actor model" ``` After publishing, verify the repo at: ```text https://huggingface.co/build-small-hackathon/AI-Puppet-Theater-MiniCPM5-Actor-GGUF ``` ## Hugging Face Publishing Publishing scripts are provided for the Actor SFT dataset and current best MiniCPM5 Actor LoRA adapter. They default to dry-run mode and do not upload unless you explicitly pass `--no-dry_run` with Hugging Face authentication available. Install or use an environment with `huggingface_hub` available. It is included in `finetune/requirements-train.txt`. Authenticate only when you are ready to upload: ```bash hf auth login ``` Dry-run the dataset publish plan: ```bash python finetune/scripts/publish_actor_sft_dataset.py --dry_run ``` Dry-run the adapter publish plan: ```bash python finetune/scripts/publish_actor_lora_adapter.py \ --adapter_dir finetune/minicpm5-actor-lora \ --dry_run ``` The adapter script uploads only the deployable adapter files by default. Checkpoint directories and optimizer state are skipped unless you explicitly pass `--include_checkpoints`. Default target repos are placeholders and may need to change based on org permissions: - dataset: `build-small-hackathon/AI-Puppet-Theater-Actor-SFT` - model: `build-small-hackathon/AI-Puppet-Theater-MiniCPM5-Actor-LoRA` Actual upload commands, when intentionally publishing: ```bash python finetune/scripts/publish_actor_sft_dataset.py \ --repo_id build-small-hackathon/AI-Puppet-Theater-Actor-SFT \ --no-dry_run python finetune/scripts/publish_actor_lora_adapter.py \ --repo_id build-small-hackathon/AI-Puppet-Theater-MiniCPM5-Actor-LoRA \ --adapter_dir finetune/minicpm5-actor-lora \ --no-dry_run ``` The scripts fail clearly without `HF_TOKEN` or `HUGGINGFACEHUB_API_TOKEN` for real uploads. They upload the prepared cards from: - `finetune/dataset_cards/actor_sft_README.md` - `finetune/model_cards/actor_lora_v0_README.md` After publishing, paste the actual dataset/model URLs into the main Space README. The v0 adapter is currently the best candidate; v1 remains a hardening experiment and tooling path unless a later eval shows it outperforming v0.