> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuneplane.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 数据形状与按设计血缘切分

> 平台能检查的九种行形状，以及为什么随机切分一份工程语料会泄漏。

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
data:
  quality:
    group_key: design            # 一行属于什么
    max_group_leak_ratio: 0.0    # 留出集里最多多少可以和训练集共用一个
```

这里的数据集就是一堆 JSON 对象，仅此而已：平台存它、剖析它、扫描它、把它交给框架，一行里*是什么*
是框架的事。这是正确的默认，而它只在一个地方不再正确 —— 有人为一份数据挑了 recipe，然后在训练跑
到第四十分钟时才发现行是偏好对、而 recipe 要的是对话。

这一页讲平台后来学会对语料内部说的两件事：它的行声称是什么形状，以及一行*属于*什么，好让切分能整
个留出一个设计，而不是留出它的一个随机样本。

## 九种形状

形状是**声明的，不是猜的**，检查器只验证这个声明。另一条路是推断，而它在要紧的那一点上更糟：读三
行去猜的启发式几乎总是对的，而那个「几乎」意味着一次无声的错训，而不是一次拒绝。

这里没有 `generic`，也没有 `custom`。什么都不意味着的格式无法被检查，只会变成一个要填的字段；形状
不在这份名单上的语料，干脆什么都不声明，其余一切照旧。

**`messages`** —— 对话 SFT。非空列表，每一项都带非空的 `role` 和 `content`。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"messages": [{"role": "user", "content": "Explain clock domain crossing"}, {"role": "assistant", "content": "A CDC is ..."}]}
```

**`prompt_completion`** —— 普通的有监督文本。两个键都是非空字符串。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"prompt": "Write a 4-bit counter", "completion": "module counter(...);\n..."}
```

**`preference`** —— DPO 及其同类。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"prompt": "Review this always block", "chosen": "The sensitivity list ...", "rejected": "Looks fine."}
```

**`fim`** —— 中间填充，代码模型学会在文件*内部*补全而不是在末尾续写靠的就是它。只有 `middle` 必须
非空：`prefix` 和 `suffix` 是上下文，在文件边缘任一侧为空都合理。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"prefix": "always @(posedge clk) begin\n", "middle": "  q <= d;\n", "suffix": "end\n"}
```

**`repo_context`** —— 前面摆着仓库其余部分的一次补全。`files` 是非空的 `{path, content}` 列表，
**路径不许重复**，外加一个非空的 `target`。RTL-Repo 和每个仓库级代码评测集用的都是这个形状，也是
那个一旦压平成 prompt 字符串就会丢掉「哪个文件是哪个」的形状。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"files": [{"path": "rtl/uart.v", "content": "module uart(...);"}, {"path": "rtl/pkg.vh", "content": "`define W 8"}], "target": "  assign tx = ...;"}
```

**`spec_rtl`** —— 一段自然语言规格，和实现它的模块。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"spec": "An 8-bit synchronous FIFO with full and empty flags", "rtl": "module fifo(...);\n..."}
```

**`testbench`** —— 一个设计，和验证它的 bench。方向是要紧的，而且和 `spec_rtl` 不对称：这里模型写
的是 *bench*，那是验证工程师的活。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"dut": "module alu(...);\n...", "testbench": "module tb_alu;\n  initial begin ..."}
```

**`log_root_cause`** —— 一段工具或回归日志，和出了什么问题。它既是设计侧的形状，也是制造侧的。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"log": "ERROR: [Synth 8-3352] multi-driven net ...", "root_cause": "The reset is driven from two always blocks"}
```

**`script`** —— 一个 EDA 脚本任务：Tcl、Makefile、约束文件。之所以和 `prompt_completion` 分开，是
因为让它正确的是脚本跑得起来，而不是文本读着顺。

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"request": "Constrain the 100 MHz input clock", "script": "create_clock -period 10.000 [get_ports clk]"}
```

`repo_context` 里重复的路径是拒绝而不是容忍：框架留下哪一份，决定了模型看到什么，所以那两项会在同
一个样本里对同一个文件各说各话。

## 检查器可以看什么

形状，仅此而已。哪些键在、它们装的是字符串而不是嵌套结构、成对的两半都在。

关于内容、质量、语言和长度的一概不看 —— 那些属于[质量报告](/zh-Hans/guides/datasets)，而一个开始
评判内容的格式检查，就是对你自己的数据发表第二种意见。

## 一致性是比例，不是门禁

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
from tuneplane.data.formats import DataFormat, conformance, parse

report = conformance(rows, DataFormat.SPEC_RTL)
print(report.rows, report.conforming, report.ratio)
for row_number, problem in report.examples:
    print(row_number, problem)
```

`conformance` 接受任何可迭代的行，所以读多少由调用方决定。它报告看了多少行、多少行合规、比例，以及
最多十条带行号的问题 —— 一份有一百万行坏数据的语料只有一个问题，不是一百万个。

两处刻意的拒绝：

* **它从不变成判决。** 见到任何一条就触发的门禁，是一周之内就会被人关掉的门禁，而真实语料里就是有
  一些没人想为之争论的坏行。平台欠你的是那个数字和头几个例子。
* **空文件没有比例。** 零行时 `ratio` 是 `None`，而不是 `1.0`。什么都没检查，「完美」是错的说法。

对没有声明的格式，`parse` 返回 `None` —— 这是常态，不是错误 —— 对不认识的名字则抛错并附上已知名字
的排序列表。

## 随机切分一份工程语料会泄漏

平台已有的每一项污染检查比的都是**文本**。`tp dataset check` 匹配归一化后的 13-gram；指纹比对取的
是采样 n-gram 哈希的交集。两者都回答了各自被造出来要回答的问题，而都看不见那个会让工程团队的评测报
废的失败。

工程语料里满是同一个产物的近似变体：一个 UART 的八个修订、一个模块和实例化它的 wrapper、同一个
testbench 参数化出的四份。一个模块的两个修订之间确实几乎没有共同的 n-gram，而就「衡量模型学到了什
么」而言，它们是**同一道题**。

随机切分它们，评测集里装的就是训练集的兄弟。每一行都不同。重复率不会响。shingle 重叠读起来是干净
的。于是留出分数量的是模型早已见过的东西的记忆，数字涨了，模型没变好。

所以这项检查比的是身份而不是文本：`group_key` 点名的任何东西 —— 一个设计、一个仓库、一个工单号 ——
答案是评测*组*里有多大比例也出现在训练集中。

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
from tuneplane.data.contamination import group_leak

leak = group_leak(train_groups=["uart", "spi"], eval_groups=["uart", "pcie"])
leak.ratio     # 0.5 —— 留出的设计有一半被训过
leak.clean     # False
leak.examples  # ("uart",)
```

三个值得知道的性质：

* **它按不同的组计数，不按行。** 一个设计出现在一千条评测行里是一次泄漏。数一千次，就等于让语料的
  行分布来决定它的泄漏看起来有多严重。
* **什么都没留出时 `ratio` 是 `None`**，不是 `0.0`，和空的一致性报告没有比例是同一个理由。
* **它完全不需要文本。** 两个集合求交集，所以在两边都还没被读之前，它就能在元数据上跑。

对一个留出集来说，零是唯一值得瞄准的值。高于零就意味着分数里有一部分是记忆。

## 声明分组键

`spec.data.quality` 是 JobSpec 里承载训练数据上限的那一块 —— 评测门禁的对偶：那些判出来的模型够不
够好，这些判进去的数据够不够干净。其中两个字段关于血缘：

| 字段                     | 含义                                                  |
| ---------------------- | --------------------------------------------------- |
| `group_key`            | 点名一行*属于*什么的元数据字段。留空就是行级，也就是此前的行为，对由独立样本组成的语料这是正确的默认 |
| `max_group_leak_ratio` | 切分两侧共同出现的组所占比例的上限，`0`–`1`                           |

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
data:
  quality:
    max_duplicate_ratio: 0.1
    group_key: design
    max_group_leak_ratio: 0.0
```

契约会直接拒绝两种自相矛盾的 spec：

* 比例不在 `0`–`1` 之间。
* 有 `max_group_leak_ratio` 却没有 `group_key` —— 没有它，每一行都是自己一组，上限永远不可能被突
  破，那就是一条永远不会被判的线。同一条规则早就把 `max_overlap_ratio` 绑在了 `overlap_with` 上。

随附的四个方案都声明了切分字段：两个 RTL 的用 `design`，脚本助手用 `repo`，日志定责用 `case` ——
同一个故障被报了两次，是一件事实。`tp solution show` 把它打印成 **split by**；见
[工程方案](/zh-Hans/guides/solutions)。

## 哪些通了，哪些没通

把这件事说准，比这个功能本身更要紧。

| 部件                                        | 状态                                                                                                      |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| 九种形状及其校验器                                 | 在 SDK 里，`tuneplane.data.formats`，有测试                                                                    |
| `group_leak`                              | 在 SDK 里，`tuneplane.data.contamination`，有测试                                                              |
| `group_key`、`max_group_leak_ratio`        | JobSpec 契约会解析并校验                                                                                        |
| 方案的 `data_format`                         | 加载清单时会对着九种校验                                                                                            |
| `max_duplicate_ratio`、`max_overlap_ratio` | 提交时对着已存的质量报告与指纹强制执行                                                                                     |
| 提交时的组泄漏检查                                 | **没有实现。** 没有任何代码去算这个比例，所以声明的上限拒绝不了任何东西                                                                  |
| 数据集版本上声明的格式                               | **没有实现。** `tp dataset push` 没有格式参数，也没有数据集字段承载它                                                          |
| 实验配置里的 `data.quality` 块                   | **`tp submit` 不带它。** CLI 从数据集与路径参数构造 JobSpec 的 data 段，`quality` 保持默认值，所以这一块只能通过直接对 API 构造的 JobSpec 到达平台 |

所以今天，这九个名字是工程方案会声明的一套词汇，外加一个你可以在自己的行上跑的库函数 —— 在预处理
脚本里，或者在推送前的 CI 里。`group_key` 同理：声明它记录了意图、并会拒掉自相矛盾的 spec；算泄漏
是 `group_leak` 的活，调用它是你的活。

这个说法比「平台会拦住一次泄漏的切分」小，而它是真的那一个。

## 下一步

[数据集](/zh-Hans/guides/datasets) · [工程方案](/zh-Hans/guides/solutions) ·
[RTL 评测](/zh-Hans/guides/rtl-benchmarks)
