技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
llm-distillation【数据与研究】Distill knowledge from large teacher LLMs into small student models via sequence-level (response-based) knowledge distillation. Covers teacher selection and licensing checks, generating distillation data with Bedrock serverless teachers (DeepSeek-R1, Claude via converse), reasoning distillation (keeping <think>/chain-of-thought for math/code/puzzle d…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Sequence-level knowledge distillation (KD) trains a small student model on the outputs of a large teacher model: generate responses with the teacher, curate them, then fine-tune the student on the curated pairs with plain SFT. No logits, no special loss — the entire "distillation" lives in the data. This is how the official DeepSeek-R1-Distill models were made, and it is the cheapest path to a small model that inherits a large model's behavior on a narrow domain.
This skill owns the data side: teacher generation, curation, and quality gates.
The training job itself (QLoRA/SFT on SageMaker or local GPU) belongs to llm-fine-tuning —
this skill's deliverable is a curated JSONL that skill can consume directly.
Select Teacher → Check License → Generate → Curate → Format → [llm-fine-tuning] → Evaluate
│ │ │ │ │ │ │
Reasoning vs MIT/Apache OK Bedrock Dedup TRL QLoRA SFT Student vs
non-reasoning ToS may forbid converse Decontam messages (out of teacher gates
teacher distillation + retry Verify JSONL scope here) (RELATIVE)
+ backoff Judge
Pick the teacher by domain, then verify you are allowed to distill from it before generating a single token:
| Teacher (Bedrock serverless) | Model / profile ID | Distillation allowed? |
|---|---|---|
| DeepSeek-R1 (reasoning) | us.deepseek.r1-v1:0 | Yes — MIT license explicitly permits distillation |
| Claude (general) | global.anthropic.claude-sonnet-5 | Check current Anthropic ToS — historically restricts training competing models |
| Llama 3.x/4 (general) | us.meta.llama3-3-70b-instruct-v1:0 | Yes with attribution — community license requires "Built with Llama" naming for derivatives |
Rule of thumb: open-weight models with MIT/Apache-2.0 (DeepSeek, Qwen) are safe teachers; proprietary API models often forbid using outputs to train models — read the provider ToS, not just the marketing page. Record the teacher ID and license decision in the dataset card.
# Confirm the inference profile is available in your region (region-agnostic: uses AWS_REGION)
aws bedrock list-inference-profiles \
--query "inferenceProfileSummaries[?contains(inferenceProfileId, 'deepseek')].inferenceProfileId"
import time
import boto3
from botocore.config import Config
# Region comes from AWS_REGION/profile - never hardcode
bedrock = boto3.client("bedrock-runtime",
config=Config(retries={"max_attempts": 0})) # we own retry policy
TEACHER_ID = "us.deepseek.r1-v1:0" # cross-region inference profile
def generate_teacher_response(prompt, max_retries=6):
"""One teacher call with exponential backoff on throttling."""
for attempt in range(max_retries):
try:
resp = bedrock.converse(
modelId=TEACHER_ID,
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={
"maxTokens": 8192, # reasoning traces are LONG; don't truncate mid-think
"temperature": 0.6, # R1 recommended range 0.5-0.7; 0 causes repetition loops
"topP": 0.95,
},
)
blocks = resp["output"]["message"]["content"]
# R1 via Converse returns reasoning in a reasoningContent block + answer in text
reasoning = next((b["reasoningContent"]["reasoningText"]["text"]
for b in blocks if "reasoningContent" in b), "")
answer = next((b["text"] for b in blocks if "text" in b), "")
return {"reasoning": reasoning, "answer": answer,
"usage": resp["usage"], "stop": resp["stopReason"]}
except bedrock.exceptions.ThrottlingException:
time.sleep(min(2 ** attempt, 60)) # 1,2,4,...60s
raise RuntimeError(f"Throttled {max_retries} times - lower concurrency")
Generation guidance:
stopReason check: drop any sample with stopReason == "max_tokens" — a truncated
reasoning chain teaches the student to trail off.scripts/generate_teacher_data.py).Cost estimation per 1k samples (do this before launching a 100k-sample run):
cost_per_1k = 1000 * (avg_input_tokens * price_in + avg_output_tokens * price_out) / 1e6
# Example: DeepSeek-R1 on Bedrock ~$1.35/M in, ~$5.40/M out (verify current pricing).
# Reasoning outputs average 2k-6k tokens: 1000 * (300*1.35 + 4000*5.40)/1e6 ≈ $22 per 1k samples.
# 3 samples/prompt for rejection sampling triples that. Budget first.
The single most important design choice in sequence-level KD:
What is the student's task domain?
├─ Multi-step reasoning (math, code gen, ARC-style grid puzzles, planning)
│ └─ KEEP the teacher's <think>/reasoning in the training target
│ (this is exactly how R1-Distill-Qwen models were trained; the student
│ learns to reason, not just to answer)
│ Target = "<think>\n{reasoning}\n</think>\n\n{answer}"
├─ Simple QA, summarization, classification, extraction
│ └─ STRIP reasoning, train on final answers only
│ (CoT adds latency and tokens at inference with no accuracy gain on
│ tasks the student can do in one step)
└─ Mixed / unsure
└─ Run a 200-sample pilot both ways, compare student accuracy AND
inference token cost; reasoning targets typically cost 5-20x more
output tokens at inference
Two hard constraints when keeping reasoning:
max_length, the sample is truncated and the student learns unterminated
rambling. Filter out samples longer than ~90% of the student's context window
(in student-tokenizer tokens, not teacher tokens).<think>...</think>); mixed formats destroy format-validity at inference.Apply in this order (each stage feeds the next — see scripts/curate_distillation_data.py):
stopReason was end_turn. Reject malformed samples outright.llm-data-preparation).llm-fine-tuning)This skill produces data; llm-fine-tuning owns the training job. The contract is a
JSONL in TRL messages format that SFTTrainer consumes without modification:
{"messages": [
{"role": "user", "content": "Solve: If 3x + 7 = 22, what is x? Put the final answer in \\boxed{}."},
{"role": "assistant", "content": "<think>\n3x + 7 = 22, so 3x = 15, x = 5.\n</think>\n\n\\boxed{5}"}
]}
Handoff notes for the fine-tuning side:
llm-fine-tuning sections 1-3).
No KL loss, no teacher logits, no special trainer.max_length to cover the 95th-percentile sample length measured in
the student's tokenizer; you already filtered outliers in step 3.Gates are relative to the teacher, never absolute — a 1.7B student will not match a 671B teacher's absolute scores, and pretending otherwise makes every distillation "fail":
| Gate | Threshold | How |
|---|---|---|
| Relative solve rate | student >= 0.80 x teacher solve rate on held-out set | Same prompts, same verifier, both models |
| Format validity | >= 0.98 of student outputs parse (closed </think>, extractable answer) | Regex/parser over student generations |
| Decontamination proof | 0 eval prompts with >= 13-gram overlap vs training set | Re-run decontamination against final training JSONL |
| No judge-only wins | Verifiable metrics agree with judge direction | If judge says "improved" but exact-match dropped, trust exact-match |
Report both numbers ("teacher 84%, student 71%, ratio 0.85 — pass"), and evaluate at the same sampling settings you will deploy the student with. Baseline the un-tuned student too: if base student = 45% and distilled = 71%, distillation added 26 points; without the baseline you cannot attribute the gain.
Lessons from an agent-orchestrated QLoRA distillation run (Qwen3-1.7B student, SageMaker
ml.g5.2xlarge) where the eval is a verifiable-reward scorer, not an LLM judge:
generations.jsonl incrementally (an interrupted run
still yields a scorable partial file); the scorer runs anywhere without torch. A scoring
bug then never costs GPU time to re-fix, and the scorer stays unit-testable.<think> chain and can spend the entire token budget inside it,
emitting no answer — scored naively that reads as "the base model can't do the task" when
the real cause is decoding budget, which inflates the apparent distillation lift. Pass
enable_thinking=False for the baseline, and verify the chat template actually honors
the flag before spending GPU (templates accept unknown Jinja vars in silence; on the
real Qwen3 template only False changes the rendering — True and absent are identical).max_train_seconds) so a hang
converts to a checkpointed stop, not an open-ended bill.llm-evaluation §6 (Negative Controls): an oracle self-test cannot catch a scorer that
accepts too much, and format-validity is the tiebreak metric precisely when both solve
rates are 0.000 — the expected case for a small student on a hard benchmark.stopReason == "max_tokens" samples are poisonscripts/generate_teacher_data.py - Batch teacher generation via Bedrock converse with retry/backoff, checkpointing, and cost estimationscripts/curate_distillation_data.py - Curation pipeline: format validation, dedup, decontamination, answer verification, judge filtering, TRL-format exportSee references/REFERENCE.md for teacher/license comparisons, reasoning-mode guidance, cost math, and quality-gate details.
Upstream: llm-data-preparation (seed prompts, dedup machinery) · Downstream: llm-fine-tuning (trains the student on this skill's JSONL), then llm-evaluation (student-vs-teacher gates)
See also: llm-cost-optimization — distillation is itself a cost lever (replace frontier calls with the student) · llm-deployment for serving the distilled student