初始化项目,由ModelHub XC社区提供模型
Model: gauthierpiarrette/nl2jq-qwen3-0.6b Source: Original Platform
This commit is contained in:
36
.gitattributes
vendored
Normal file
36
.gitattributes
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.arrow filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bin filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ftz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.h5 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.joblib filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.model filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.npy filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.npz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.onnx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ot filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.parquet filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pb filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pickle filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pkl filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pt filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pth filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.rar filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
||||||
|
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tar filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tflite filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.wasm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.zst filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
||||||
112
README.md
Normal file
112
README.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
license: apache-2.0
|
||||||
|
language:
|
||||||
|
- en
|
||||||
|
pipeline_tag: text-generation
|
||||||
|
base_model: Qwen/Qwen3-0.6B
|
||||||
|
tags:
|
||||||
|
- jq
|
||||||
|
- code-generation
|
||||||
|
- json
|
||||||
|
- lora
|
||||||
|
datasets:
|
||||||
|
- gauthierpiarrette/nl2jq
|
||||||
|
metrics:
|
||||||
|
- execution-accuracy
|
||||||
|
---
|
||||||
|
|
||||||
|
# nl2jq-qwen3-0.6b
|
||||||
|
|
||||||
|
*Part of the **nl2jq** project — [all artifacts](https://huggingface.co/collections/gauthierpiarrette/nl2jq-natural-language-to-jq-locally-6a5299203df3832415275223) · [code + CLI](https://github.com/gauthierpiarrette/nl2jq) · [live demo](https://huggingface.co/spaces/gauthierpiarrette/nl2jq) · [benchmark](https://huggingface.co/datasets/gauthierpiarrette/nl2jq-bench)*
|
||||||
|
|
||||||
|
A LoRA fine-tune of [Qwen3-0.6B](https://huggingface.co/Qwen/Qwen3-0.6B) that translates a
|
||||||
|
natural-language request plus a JSON sample into a [jq](https://jqlang.github.io/jq/)
|
||||||
|
program. It is the **practical baseline** companion to the from-scratch
|
||||||
|
[`nl2jq-40m`](https://huggingface.co/gauthierpiarrette/nl2jq-40m): larger, but its
|
||||||
|
pretrained language priors make it markedly more reliable at **copying novel field names**
|
||||||
|
from your JSON — which is what most real CLI use needs.
|
||||||
|
|
||||||
|
The LoRA adapter has been merged into the base weights, so it loads as an ordinary model.
|
||||||
|
|
||||||
|
**This is the recommended default backend of the `jqgen` CLI** — the best
|
||||||
|
latency/accuracy balance for local use (~1–3s per query on laptop CPU).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat data.json | jqgen --backend qwen "total spend per customer, paid orders only"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or directly:
|
||||||
|
```python
|
||||||
|
import re
|
||||||
|
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||||
|
|
||||||
|
tok = AutoTokenizer.from_pretrained("gauthierpiarrette/nl2jq-qwen3-0.6b")
|
||||||
|
model = AutoModelForCausalLM.from_pretrained("gauthierpiarrette/nl2jq-qwen3-0.6b")
|
||||||
|
msgs = [{"role": "system", "content": "You translate a natural-language request plus a "
|
||||||
|
"sample of JSON into a single jq program. Output only the jq program, nothing else."},
|
||||||
|
{"role": "user", "content": "Request: highest paid person\nJSON sample: [...]"}]
|
||||||
|
enc = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt")
|
||||||
|
ids = enc["input_ids"] # newer transformers return a BatchEncoding here, not a tensor
|
||||||
|
out = tok.decode(model.generate(ids, max_new_tokens=128)[0][ids.shape[1]:],
|
||||||
|
skip_special_tokens=True)
|
||||||
|
# Qwen3 emits an (empty) reasoning block before the program — strip it:
|
||||||
|
print(re.sub(r"(?s)^\s*<think>.*?</think>\s*", "", out).strip())
|
||||||
|
```
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
Execution accuracy on the frozen 400-item
|
||||||
|
[`nl2jq-bench v1.0.0`](https://huggingface.co/datasets/gauthierpiarrette/nl2jq-bench)
|
||||||
|
(0% field overlap with training, novel domains, one-shot evaluation). **These weights are
|
||||||
|
the v6 fine-tune** — trained on data with per-example-unique field names and coverage of
|
||||||
|
`reduce`/`foreach`/`walk`/`paths`/`if` constructs; the superseded v5 row is kept for the
|
||||||
|
record:
|
||||||
|
|
||||||
|
| Model | frozen pass@1 | valid | T1 | T2 | T3 | T4 | T5-gen |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| **this model (v6)** | **0.40** | 0.73 | 0.65 | 0.52 | 0.36 | 0.28 | **0.20** |
|
||||||
|
| v5 fine-tune (superseded) | 0.24 | 0.67 | 0.45 | 0.38 | 0.21 | 0.12 | 0.02 |
|
||||||
|
| Qwen3-0.6B base (zero-shot) | 0.01 | 0.06 | 0.02 | 0.00 | 0.00 | 0.00 | 0.02 |
|
||||||
|
| [nl2jq-qwen3.5-2b](https://huggingface.co/gauthierpiarrette/nl2jq-qwen3.5-2b) (bigger sibling, CLI config) | 0.48 | 0.82 | 0.75 | 0.61 | 0.47 | 0.35 | 0.18 |
|
||||||
|
| Claude Opus 4.8 (zero-shot, context row) | 0.96 | 0.98 | 1.00 | 0.96 | 0.96 | 0.96 | 0.90 |
|
||||||
|
|
||||||
|
Three honest readings:
|
||||||
|
|
||||||
|
- **Fine-tuning does all the work at this scale**: the base model zero-shot scores 0.01
|
||||||
|
(it emits pseudo-code, not jq); this LoRA takes it to 0.40 on held-out fields.
|
||||||
|
- **The v6 data fix mattered more than anything else**: same base, same LoRA recipe,
|
||||||
|
+67% pass@1 and 10× T5 purely from making field-copying the only winning strategy in
|
||||||
|
training.
|
||||||
|
- **This is the fast backend, not the most accurate one**: it answers in ~1–3s on laptop
|
||||||
|
CPU; the 2B sibling is +8 points at ~3× the latency. Both remain far from the frontier's
|
||||||
|
0.96 — that gap is disclosed, not hidden.
|
||||||
|
|
||||||
|
## Training
|
||||||
|
|
||||||
|
- **Base**: Qwen3-0.6B. **Method**: LoRA (r=32, α=64, all-linear targets), 1 epoch over
|
||||||
|
150k execution-verified examples, cosine schedule, bf16, gradient checkpointing.
|
||||||
|
- **Data**: the v6 generation of the synthetic pipeline — per-example-unique field names
|
||||||
|
(so copying from the input, not vocabulary recall, is the only winning strategy) plus
|
||||||
|
generators for `reduce`/`foreach`/`walk`/`paths`/`if`-banding constructs. 100% synthetic
|
||||||
|
and execution-verified — see the
|
||||||
|
[nl2jq dataset](https://huggingface.co/datasets/gauthierpiarrette/nl2jq). No web text, no
|
||||||
|
scraped code. Every program was run under jq 1.7.1 and kept only if it produced
|
||||||
|
non-degenerate output.
|
||||||
|
- Frontier-model outputs are **never** used as training targets; all natural-language
|
||||||
|
phrasings are template/grammar-generated by the author.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- jq 1.7.1 only; no streaming, modules, or user-defined functions.
|
||||||
|
- Ambiguous requests may yield a valid program that answers a different reading.
|
||||||
|
- **Inspect before trusting the output** — the `jqgen` CLI prints the program to stderr.
|
||||||
|
jq runs locally with no network access, but an incorrect program can still mislead.
|
||||||
|
- Not a general assistant — it emits jq and nothing else.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Apache-2.0 (inherits the base model's license). Fine-tuned on CC BY 4.0 synthetic data.
|
||||||
|
|
||||||
|
Code, benchmark tooling, and training pipeline: [github.com/gauthierpiarrette/nl2jq](https://github.com/gauthierpiarrette/nl2jq)
|
||||||
89
chat_template.jinja
Normal file
89
chat_template.jinja
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{%- if tools %}
|
||||||
|
{{- '<|im_start|>system\n' }}
|
||||||
|
{%- if messages[0].role == 'system' %}
|
||||||
|
{{- messages[0].content + '\n\n' }}
|
||||||
|
{%- endif %}
|
||||||
|
{{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
|
||||||
|
{%- for tool in tools %}
|
||||||
|
{{- "\n" }}
|
||||||
|
{{- tool | tojson }}
|
||||||
|
{%- endfor %}
|
||||||
|
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
|
||||||
|
{%- else %}
|
||||||
|
{%- if messages[0].role == 'system' %}
|
||||||
|
{{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
|
||||||
|
{%- for message in messages[::-1] %}
|
||||||
|
{%- set index = (messages|length - 1) - loop.index0 %}
|
||||||
|
{%- if ns.multi_step_tool and message.role == "user" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}
|
||||||
|
{%- set ns.multi_step_tool = false %}
|
||||||
|
{%- set ns.last_query_index = index %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- for message in messages %}
|
||||||
|
{%- if message.content is string %}
|
||||||
|
{%- set content = message.content %}
|
||||||
|
{%- else %}
|
||||||
|
{%- set content = '' %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
|
||||||
|
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
|
||||||
|
{%- elif message.role == "assistant" %}
|
||||||
|
{%- set reasoning_content = '' %}
|
||||||
|
{%- if message.reasoning_content is string %}
|
||||||
|
{%- set reasoning_content = message.reasoning_content %}
|
||||||
|
{%- else %}
|
||||||
|
{%- if '</think>' in content %}
|
||||||
|
{%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
|
||||||
|
{%- set content = content.split('</think>')[-1].lstrip('\n') %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if loop.index0 > ns.last_query_index %}
|
||||||
|
{%- if loop.last or (not loop.last and reasoning_content) %}
|
||||||
|
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
|
||||||
|
{%- else %}
|
||||||
|
{{- '<|im_start|>' + message.role + '\n' + content }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- else %}
|
||||||
|
{{- '<|im_start|>' + message.role + '\n' + content }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if message.tool_calls %}
|
||||||
|
{%- for tool_call in message.tool_calls %}
|
||||||
|
{%- if (loop.first and content) or (not loop.first) %}
|
||||||
|
{{- '\n' }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if tool_call.function %}
|
||||||
|
{%- set tool_call = tool_call.function %}
|
||||||
|
{%- endif %}
|
||||||
|
{{- '<tool_call>\n{"name": "' }}
|
||||||
|
{{- tool_call.name }}
|
||||||
|
{{- '", "arguments": ' }}
|
||||||
|
{%- if tool_call.arguments is string %}
|
||||||
|
{{- tool_call.arguments }}
|
||||||
|
{%- else %}
|
||||||
|
{{- tool_call.arguments | tojson }}
|
||||||
|
{%- endif %}
|
||||||
|
{{- '}\n</tool_call>' }}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
{{- '<|im_end|>\n' }}
|
||||||
|
{%- elif message.role == "tool" %}
|
||||||
|
{%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
|
||||||
|
{{- '<|im_start|>user' }}
|
||||||
|
{%- endif %}
|
||||||
|
{{- '\n<tool_response>\n' }}
|
||||||
|
{{- content }}
|
||||||
|
{{- '\n</tool_response>' }}
|
||||||
|
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
|
||||||
|
{{- '<|im_end|>\n' }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- if add_generation_prompt %}
|
||||||
|
{{- '<|im_start|>assistant\n' }}
|
||||||
|
{%- if enable_thinking is defined and enable_thinking is false %}
|
||||||
|
{{- '<think>\n\n</think>\n\n' }}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
63
config.json
Normal file
63
config.json
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"architectures": [
|
||||||
|
"Qwen3ForCausalLM"
|
||||||
|
],
|
||||||
|
"attention_bias": false,
|
||||||
|
"attention_dropout": 0.0,
|
||||||
|
"bos_token_id": 151643,
|
||||||
|
"dtype": "bfloat16",
|
||||||
|
"eos_token_id": 151645,
|
||||||
|
"head_dim": 128,
|
||||||
|
"hidden_act": "silu",
|
||||||
|
"hidden_size": 1024,
|
||||||
|
"initializer_range": 0.02,
|
||||||
|
"intermediate_size": 3072,
|
||||||
|
"layer_types": [
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention",
|
||||||
|
"full_attention"
|
||||||
|
],
|
||||||
|
"max_position_embeddings": 40960,
|
||||||
|
"max_window_layers": 28,
|
||||||
|
"model_type": "qwen3",
|
||||||
|
"num_attention_heads": 16,
|
||||||
|
"num_hidden_layers": 28,
|
||||||
|
"num_key_value_heads": 8,
|
||||||
|
"pad_token_id": null,
|
||||||
|
"rms_norm_eps": 1e-06,
|
||||||
|
"rope_parameters": {
|
||||||
|
"rope_theta": 1000000,
|
||||||
|
"rope_type": "default"
|
||||||
|
},
|
||||||
|
"sliding_window": null,
|
||||||
|
"tie_word_embeddings": true,
|
||||||
|
"transformers_version": "5.13.0",
|
||||||
|
"use_cache": true,
|
||||||
|
"use_sliding_window": false,
|
||||||
|
"vocab_size": 151936
|
||||||
|
}
|
||||||
25
eval_results.json
Normal file
25
eval_results.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"benchmark": "gauthierpiarrette/nl2jq-bench",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"freeze_sha256": "25e7c01360707b30df93e7c1ed26997b3bb3ee7840a093d024bb4d16f489c5de",
|
||||||
|
"jq_version": "1.7.1",
|
||||||
|
"n_items": 400,
|
||||||
|
"protocol": "one-shot; no selection or iteration against the frozen set",
|
||||||
|
"model": "gauthierpiarrette/nl2jq-qwen3-0.6b",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"system": "nl2jq-qwen3-0.6b-v6",
|
||||||
|
"decoding": "greedy",
|
||||||
|
"pass_at_1": 0.4,
|
||||||
|
"valid_at_1": 0.73,
|
||||||
|
"tiers": {
|
||||||
|
"T1": 0.65,
|
||||||
|
"T2": 0.52,
|
||||||
|
"T3": 0.36,
|
||||||
|
"T4": 0.28,
|
||||||
|
"T5": 0.2
|
||||||
|
},
|
||||||
|
"t5_generalization": 0.2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
generation_config.json
Normal file
13
generation_config.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"bos_token_id": 151643,
|
||||||
|
"do_sample": true,
|
||||||
|
"eos_token_id": [
|
||||||
|
151645,
|
||||||
|
151643
|
||||||
|
],
|
||||||
|
"pad_token_id": 151643,
|
||||||
|
"temperature": 0.6,
|
||||||
|
"top_k": 20,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"transformers_version": "5.13.0"
|
||||||
|
}
|
||||||
3
model.safetensors
Normal file
3
model.safetensors
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1b9f227b582c165fd64dd7c090646c9617c814e5268d84226576701ee28eba41
|
||||||
|
size 1192135096
|
||||||
3
tokenizer.json
Normal file
3
tokenizer.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:be75606093db2094d7cd20f3c2f385c212750648bd6ea4fb2bf507a6a4c55506
|
||||||
|
size 11422650
|
||||||
30
tokenizer_config.json
Normal file
30
tokenizer_config.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"add_prefix_space": false,
|
||||||
|
"backend": "tokenizers",
|
||||||
|
"bos_token": null,
|
||||||
|
"clean_up_tokenization_spaces": false,
|
||||||
|
"eos_token": "<|im_end|>",
|
||||||
|
"errors": "replace",
|
||||||
|
"extra_special_tokens": [
|
||||||
|
"<|im_start|>",
|
||||||
|
"<|im_end|>",
|
||||||
|
"<|object_ref_start|>",
|
||||||
|
"<|object_ref_end|>",
|
||||||
|
"<|box_start|>",
|
||||||
|
"<|box_end|>",
|
||||||
|
"<|quad_start|>",
|
||||||
|
"<|quad_end|>",
|
||||||
|
"<|vision_start|>",
|
||||||
|
"<|vision_end|>",
|
||||||
|
"<|vision_pad|>",
|
||||||
|
"<|image_pad|>",
|
||||||
|
"<|video_pad|>"
|
||||||
|
],
|
||||||
|
"is_local": true,
|
||||||
|
"local_files_only": true,
|
||||||
|
"model_max_length": 131072,
|
||||||
|
"pad_token": "<|endoftext|>",
|
||||||
|
"split_special_tokens": false,
|
||||||
|
"tokenizer_class": "Qwen2Tokenizer",
|
||||||
|
"unk_token": null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user