Interview_Assistant / index.html
Shawnno's picture
Upload index.html with huggingface_hub
1090b3e verified
Raw History Blame Contribute Delete
27 kB
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Qwen2.5-3B 面试助手 · SFT + DPO 微调对比</title>
<style>
:root {
--bg: #f7f8fa; --card: #ffffff; --ink: #1a1d23; --muted: #6b7280;
--line: #e5e7eb; --base: #6b7280; --sft: #2563eb; --dpo: #059669;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--ink);
font: 15px/1.7 -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased; }
header { background: linear-gradient(135deg,#0f172a,#1e3a8a); color:#fff; padding: 40px 20px 36px; }
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 20px; }
header h1 { margin: 0 0 8px; font-size: 26px; letter-spacing: .3px; }
header p { margin: 0; color: #cbd5e1; font-size: 14px; }
.badges { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 8px; }
.badge { font-size: 12px; padding: 4px 10px; border-radius: 999px; border: 1px solid #334155; color:#dbeafe; }
main { padding: 28px 0 48px; }
section { margin-bottom: 28px; }
h2 { font-size: 17px; margin: 0 0 14px; }
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.metric { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 16px; }
.metric .name { font-size: 13px; color: var(--muted); margin-bottom: 10px; }
.metric .row { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 6px; }
.metric .row:last-child { margin-bottom: 0; }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
.dot.base { background: var(--base); } .dot.sft { background: var(--sft); } .dot.dpo { background: var(--dpo); }
.metric .hint { font-size: 11px; color: #9ca3af; margin-top: 10px; }
.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.tab { border: 1px solid var(--line); background: var(--card); color: var(--muted);
padding: 8px 14px; border-radius: 999px; cursor: pointer; font-size: 13px; }
.tab.active { background: #1e3a8a; color: #fff; border-color: #1e3a8a; }
.qtitle { font-size: 16px; font-weight: 600; margin: 0 0 14px; }
.cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.col { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 16px;
display: flex; flex-direction: column; }
.col .stage { font-size: 13px; font-weight: 700; padding-bottom: 10px; margin-bottom: 10px;
border-bottom: 1px solid var(--line); display: flex; align-items: center; }
.col.base .stage { color: var(--base); } .col.sft .stage { color: var(--sft); } .col.dpo .stage { color: var(--dpo); }
.col .answer { font-size: 14px; white-space: pre-wrap; word-break: break-word; }
.col .time { margin-top: auto; padding-top: 12px; font-size: 11px; color: #9ca3af; }
pre { background: #f3f4f6; border: 1px solid var(--line); border-radius: 8px; padding: 12px;
overflow-x: auto; font: 12.5px/1.6 ui-monospace, "SF Mono", Consolas, monospace; white-space: pre-wrap; }
footer { border-top: 1px solid var(--line); padding: 24px 0; color: var(--muted); font-size: 13px; }
footer a { color: #2563eb; text-decoration: none; }
@media (max-width: 820px) {
.cols, .metrics { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<header>
<div class="wrap">
<h1>Qwen2.5-3B 面试助手 · SFT + DPO 微调对比</h1>
<p>同一道面试题,在 Qwen2.5-3B-Instruct 基座上依次经过 QLoRA SFT 与 DPO 对齐后的回答对比(4-bit 加载,贪心解码)。</p>
<div class="badges">
<span class="badge">基座 Qwen2.5-3B-Instruct</span>
<span class="badge">QLoRA SFT</span>
<span class="badge">DPO 对齐</span>
<span class="badge">reward-model 偏好对</span>
</div>
</div>
</header>
<main class="wrap">
<section>
<h2>独立测试集结果(64 题)</h2>
<div class="metrics">
<div class="metric"><div class="name">回答中位字数</div><div class="row"><span><span class="dot base"></span>Base</span><span>883</span></div><div class="row"><span><span class="dot sft"></span>SFT</span><span>345</span></div><div class="row"><span><span class="dot dpo"></span>SFT+DPO</span><span>443</span></div><div class="hint">越接近题目所需越好</div></div><div class="metric"><div class="name">自然收尾率</div><div class="row"><span><span class="dot base"></span>Base</span><span>100%</span></div><div class="row"><span><span class="dot sft"></span>SFT</span><span>100%</span></div><div class="row"><span><span class="dot dpo"></span>SFT+DPO</span><span>100%</span></div><div class="hint">越高越好</div></div><div class="metric"><div class="name">强 5-gram 重复率</div><div class="row"><span><span class="dot base"></span>Base</span><span>90.6%</span></div><div class="row"><span><span class="dot sft"></span>SFT</span><span>9.4%</span></div><div class="row"><span><span class="dot dpo"></span>SFT+DPO</span><span>10.9%</span></div><div class="hint">越低越好</div></div>
</div>
</section>
<section>
<h2>逐题三阶段对比</h2>
<div class="tabs" id="tabs"></div>
<h3 class="qtitle" id="qtitle"></h3>
<div class="cols" id="cols"></div>
</section>
</main>
<footer class="wrap">
<p>模型:<a href="https://huggingface.co/Shawnno/qwen2.5-3b-interview-sft-lora">SFT LoRA</a> ·
<a href="https://huggingface.co/Shawnno/qwen2.5-3b-interview-dpo-lora">SFT+DPO LoRA</a> ·
代码:<a href="https://github.com/kinjazA/RL">github.com/kinjazA/RL</a></p>
</footer>
<script>
const DATA = {"请解释机器学习中的偏差和方差,它们分别过高时通常会出现什么现象?": {"Base": {"answer": "在机器学习中,\"偏差\"(Bias)和\"方差\"(Variance)是衡量模型性能的重要指标。它们反映了模型对训练数据拟合的程度以及泛化能力。\n\n1. **偏差**(Bias):指的是模型对数据的拟合程度。如果模型的偏差过大,意味着模型没有很好地捕捉到数据中的模式,导致预测结果与真实值之间存在较大的差距。具体来说,当模型的偏差过大时,模型可能会过于简单,无法准确地捕捉到数据中的复杂关系,从而导致模型在训练集上表现得非常好,但在测试集或新数据上的表现较差。这种现象通常被称为“欠拟合”(Underfitting)。\n\n2. **方差**(Variance):指的是模型对训练数据过度拟合的程度。如果模型的方差过大,意味着模型对训练数据的细节过于敏感,导致模型在训练集上表现得非常好,但是一旦应用于新的、未见过的数据时,模型的表现就会大幅下降。具体来说,当模型的方差过大时,模型可能会变得非常复杂,以至于它不仅捕捉到了训练数据中的模式,还捕捉到了一些无关紧要的噪声或随机性。这种现象通常被称为“过拟合”(Overfitting)。\n\n简而言之:\n- 当模型的偏差过大时,模型可能对训练数据拟合得很好,但在新数据上表现不佳。\n- 当模型的方差过大时,模型可能对训练数据拟合得不好,但在新数据上表现很好,但这种效果往往是不可靠的,因为模型可能只是“记住”了训练数据中的噪声而不是真正的规律。\n\n为了获得一个平衡好的模型,通常需要通过调整模型复杂度、增加更多的训练数据、使用正则化等方法来减少偏差和方差。", "elapsed": 21.9}, "SFT": {"answer": "偏差就是模型对数据的拟合程度,方差是模型对训练数据变化的敏感度。偏差高时,模型太简单,比如线性回归,它根本学不到真实规律,结果就偏大、偏小都一样,这是欠拟合;方差高时,模型太复杂,比如高阶多项式,训练集和验证集差异特别大,这是过拟合。实际中一般先看训练误差和验证误差曲线,如果训练误差明显低于验证误差,说明偏差大;反之则方差大。", "elapsed": 10.9}, "SFT + DPO": {"answer": "偏差就是模型对数据的拟合程度,方差是模型对训练数据变化的敏感度。偏差高时,模型太简单,比如线性回归,把曲线压成直线,训练集上误差小,但测试集上表现差,这是欠拟合,常见现象是训练集和验证集误差都偏大,而且两者差距不大。方差高时,模型太复杂,比如高阶多项式,训练集上误差小,但换一批样本,测试集误差就上去了,这是过拟合,常见现象是训练集误差低、验证集误差高,而且验证集误差随数据量增加而波动变大。实际工程里,我会用交叉验证和学习曲线来判断,偏差高就加复杂度,方差高就加正则化或降复杂度。本质上就是模型要么太死,要么太活,死得不够就欠拟合,活得太猛就过拟合。", "elapsed": 19.8}}, "请解释 JVM 的垃圾回收机制。什么是 Minor GC、Major GC 和 Full GC?": {"Base": {"answer": "JVM(Java虚拟机)的垃圾回收机制是自动管理内存的一种方式,它能够自动检测并回收不再被使用的对象所占用的内存空间,从而避免了手动管理内存时可能出现的内存泄漏和效率低下等问题。\n\n### 垃圾回收的基本概念\n\n在Java中,垃圾回收器通过识别不再被引用的对象来决定何时进行垃圾回收。当一个对象不再有任何变量引用它时,这个对象就被称为“不可达”(Unreachable)。垃圾回收器会定期扫描整个堆内存,找出所有不可达的对象,并释放它们占用的内存空间。\n\n### Minor GC、Major GC 和 Full GC\n\n#### Minor GC(小规模垃圾回收)\n\nMinor GC 也称为“新生代垃圾回收”,主要针对的是年轻代(Young Generation)中的对象。年轻代通常包含两个区域:Eden区和两个Survivor区(通常称为From和To Survivor区)。Minor GC 主要处理Eden区以及Survivor区中的对象。当Eden区或Survivor区中的对象不再被引用时,这些对象会被标记为垃圾,然后被收集器回收。Minor GC 是一种快速且低风险的垃圾回收过程,因为它只涉及年轻代的内存。\n\n#### Major GC(大规模垃圾回收)\n\nMajor GC 也称为“老年代垃圾回收”,主要针对的是老年代(Old Generation)中的对象。老年代是专门为存储长时间存活的对象而设计的。当老年代中的对象不再被引用时,这些对象也会被标记为垃圾,然后被收集器回收。Major GC 是一种较慢但更彻底的垃圾回收过程,因为它涉及到整个老年代的内存。\n\n#### Full GC(完全垃圾回收)\n\nFull GC 是指对整个堆内存进行的垃圾回收操作,即包括年轻代和老年代的所有对象。Full GC 通常发生在以下几种情况下:\n1. 当老年代的内存使用率达到某个阈值时。\n2. 当年轻代的Survivor区已满,无法容纳新的对象时。\n3. 当系统资源紧张,需要进行一次全面的垃圾回收以释放更多的内存空间。\n\nFull GC 是最耗时的操作,因为它需要扫描整个堆内存,因此在性能上可能会带来一些影响。为了减少Full GC 的发生,可以采取一些措施,如调整垃圾回收器的参数、优化代码等。\n\n### 总结\n\n- **Minor GC**:处理年轻代中的对象,速度快且风险低。", "elapsed": 28.1}, "SFT": {"answer": "JVM的垃圾回收,核心就是判断对象“还活着”还是“该死了”。它用两个指针来区分:Eden区是新生代,Survivor区是老年代,Full GC是整个堆都扫一遍。Minor GC只扫新生代,Minor GC频繁说明对象生命周期短,比如循环引用或大量临时对象;Major GC是老年代满后才触发,Full GC是整个堆满了。\n\nMinor GC通常很快,因为只扫一小部分;Major GC可能要等几秒,因为要遍历整个老年代;Full GC最严重,因为要重建整个堆。实际中,Minor GC频率高但影响小,Major GC影响大但时间短,Full GC很少直接触发,一般通过监控发现堆已满再触发。本质上,Minor GC解决“新对象活不长”的问题,Major GC解决“老对象越来越多”的问题,Full GC是最后兜底的。", "elapsed": 19.1}, "SFT + DPO": {"answer": "JVM的垃圾回收,核心就是帮我们把“该扔的内存”给扔掉,让堆能腾出来。对象存活判断用的是引用计数和周期性扫描,但真正执行的是标记-清除。\n\nMinor GC是年轻代内部的,主要处理短生命周期的对象,比如临时创建的函数参数、循环里互相引用的小对象。它快,因为对象少、空间大,一般几毫秒就能完。Major GC是老年代的,处理长生命周期对象,比如大数组、复杂对象链表,对象多、空间小,可能要几十毫秒到几秒。Full GC是老年代和年轻代一起扫,对象特别多时,整个堆都得动,时间最长,可能十几秒甚至几分钟。\n\nMinor GC频繁说明年轻代满了,老年代压力不大;Major GC频繁说明老年代满了,年轻代也跟着吃紧。Full GC是极端情况,通常会触发并发标记和并发清理,把存活对象标记后直接移动,减少停顿。本质上,Minor GC是“快速清理”,Major GC是“全面清场”,Full GC是“最后的挣扎”。", "elapsed": 22.6}}, "有一份 CSV 文件包含 name、age、score 三列,请用 Python 读取它,按 score 降序输出前 10 名,并处理文件不存在等异常。": {"Base": {"answer": "要完成这个任务,我们需要使用 Python 的 `pandas` 库来读取 CSV 文件,并使用 `try-except` 结构来处理可能出现的异常。下面是一个完整的示例代码,包括了异常处理和排序操作。\n\n首先,确保你已经安装了 `pandas` 库。如果没有安装,可以使用以下命令进行安装:\n\n```bash\npip install pandas\n```\n\n接下来是完整的代码实现:\n\n```python\nimport pandas as pd\n\ndef read_and_sort_csv(file_path):\n try:\n # 尝试读取 CSV 文件\n df = pd.read_csv(file_path)\n \n # 按照 'score' 列降序排序\n sorted_df = df.sort_values(by='score', ascending=False)\n \n # 获取前 10 条记录\n top_10 = sorted_df.head(10)\n \n # 打印结果\n print(top_10[['name', 'age', 'score']])\n \n except FileNotFoundError:\n print(f\"Error: The file {file_path} does not exist.\")\n except Exception as e:\n print(f\"An error occurred: {e}\")\n\n# 示例调用\nread_and_sort_csv('path_to_your_file.csv')\n```\n\n在这个脚本中:\n- 使用 `pd.read_csv(file_path)` 读取 CSV 文件。\n- 使用 `sort_values(by='score', ascending=False)` 方法对数据框进行排序,使得分数从高到低排列。\n- 使用 `head(10)` 获取前 10 行数据。\n- 使用 `try-except` 结构捕获并处理可能发生的异常,如文件不存在或读取过程中出现其他错误。\n\n请将 `'path_to_your_file.csv'` 替换为你的实际文件路径。运行此脚本将会按照分数降序输出前 10 名学生的姓名、年龄和分数。如果文件不存在,会打印相应的错误信息。", "elapsed": 21.3}, "SFT": {"answer": "这个需求我直接写代码吧。\n\n```python\nimport pandas as pd\n\ntry:\n df = pd.read_csv('data.csv')\nexcept FileNotFoundError:\n print(\"文件不存在,退出\")\n exit(1)\nexcept pd.errors.EmptyDataError:\n print(\"文件为空,退出\")\n exit(2)\nexcept Exception as e:\n print(f\"读取失败:{e}\")\n exit(3)\n\n# 按 score 降序取前 10 行\ntop_10 = df.sort_values(by='score', ascending=False).head(10)\nprint(top_10[['name', 'age', 'score']])\n```\n\n这里我用了 `try-except` 处理三个常见异常:文件不存在、数据为空、其他未知错误。如果文件不存在,直接退出;如果数据为空,也退出;其他异常统一捕获并打印堆栈信息后退出。排序和取前 10 是标准操作,最后输出结果。本质上就是让程序在异常时优雅地停下来,而不是抛错让调用者去处理。", "elapsed": 20.7}, "SFT + DPO": {"answer": "这个需求我直接写代码吧。\n\n```python\nimport pandas as pd\n\ntry:\n df = pd.read_csv('data.csv')\nexcept FileNotFoundError:\n print(\"文件不存在,退出\")\n exit(1)\nexcept pd.errors.EmptyDataError:\n print(\"文件是空的,退出\")\n exit(2)\nexcept Exception as e:\n print(f\"读文件出错:{e}\")\n exit(3)\n\n# 按 score 降序取前 10 行,同时处理 age 和 score 可能缺失的情况\ntop_10 = df.sort_values(by='score', ascending=False).head(10)\nprint(top_10[['name', 'age', 'score']])\n```\n\n这里我加了三个异常分支:`FileNotFoundError` 是文件真的不存在,`pd.EmptyDataError` 是文件是空的,其他异常都归到 `Exception` 统一处理。`sort_values` 的 `by='score'` 升序默认是降序,所以 `ascending=False` 已经对齐了,不用额外翻书。`head(10)` 能自动处理索引缺失,比如 `age` 列有空值,它会跳过那行,不会把空值也塞进结果里。本质上就是先验证文件状态,再做排序,最后兜底输出。", "elapsed": 26.1}}, "请说一个你实际影响工作的短板,以及你在工作中采取的改进措施。": {"Base": {"answer": "作为AI模型,我没有实际的工作环境和直接的工作任务,因此没有实际影响工作的短板。但我可以以一个假设的情景来回答这个问题。\n\n假设我是一个负责数据分析的员工,我发现我的数据处理能力相对较弱,经常需要花费更多的时间去处理一些复杂的数据集。这导致我在项目进度上常常落后于其他同事,影响了整个团队的工作效率。\n\n为了改善这一情况,我决定采取以下措施:\n1. 学习新技能:我会利用业余时间学习新的数据分析工具和技术,如Python编程、R语言等,提高自己的数据处理能力。\n2. 请教专家:我会主动向有丰富经验的同事或导师请教,寻求他们的帮助和建议,以便更快地掌握数据处理的方法和技巧。\n3. 加强实践:我会多参与一些实际的数据分析项目,通过不断实践来提升自己的数据处理能力。同时,也会定期与团队成员分享自己的学习成果,共同进步。\n4. 制定计划:我会制定详细的数据处理计划,明确每个阶段的任务和目标,确保能够按时完成工作并保持良好的工作进度。\n5. 求助工具:我会使用一些数据分析工具,如Python的Pandas库、Matplotlib库等,来提高工作效率。同时,也会借助一些在线资源,如Kaggle、DataCamp等,来获取更多的学习资料和实战经验。\n通过这些措施,我相信可以有效提升自己的数据处理能力,从而更好地完成工作任务,提高团队整体的工作效率。", "elapsed": 16.1}, "SFT": {"answer": "我之前在数据团队做报表分析时,最明显的短板是逻辑推理不够严谨,容易被数据表面现象带偏。比如有一次帮业务方算了一个“转化率提升”的结论,当时只看点击率涨了,没去核对漏斗各环节的流失率变化,就直接下结论说策略有效。后来我意识到问题出在没有先画流程图和拆分指标口径,所以改进措施是:每次做分析前先画数据流图,明确每个字段的来源和计算逻辑,再用反事实假设去验证结论——比如假设如果改版没发生,转化率会不会还是这个水平。同时我也主动找老同事过一遍我的逻辑,把“直觉判断”变成“可回溯的推理过程”。现在我更习惯先问“为什么”,而不是急着下结论。", "elapsed": 17.7}, "SFT + DPO": {"answer": "我之前在数据团队干过,最明显的短板是统计分析时容易陷入细节,结果出来之后经常自己觉得挺靠谱,但给业务方一讲,他们就懵了。比如做用户分群,我盯着特征算得特别仔细,但业务方要的是“这个策略能带来多少转化”,我算完直接给个均值,他们问“那这个群体里谁贡献大”或者“如果砍掉某个变量,效果会差多少”,我就卡壳了。\n\n改进措施我分三步走。第一,我主动让业务方一起参与分析,把“我要什么”和“我能给什么”写成明确问题,比如“我们想提升A类用户的复购率,你能帮我们找最核心的驱动因素”。第二,我专门学了一套“反向验证”方法,就是拿现有数据里的高/低效样本,去拆它们的差异,而不是自己造规律。第三,每周固定跟业务方对齐一次,把结论翻译成业务语言,比如“这个策略在高活跃用户上转化率提升3%,但成本也涨了10%”,然后我们一起定决策边界——比如成本涨超过5%就不动了。\n\n本质上,短板不是统计能力不行,而是把统计结论翻译成业务可决策的语言的能力弱。改进的核心是让业务方从“帮我算”变成“帮我回答业务问题”,而我的角色就是翻译者。", "elapsed": 130.7}}, "什么是数据库的索引?为什么它能加快查询?它有什么代价?": {"Base": {"answer": "在数据库中,索引是一种数据结构,用于加速数据检索。它为数据库表中的一个或多个列提供了一个快速访问路径,使得查询操作能够更快地找到所需的数据。\n\n### 什么是索引?\n\n索引是通过在表的特定列上创建一个额外的数据结构来实现的。这个额外的数据结构(称为 B+ 树)允许数据库管理系统快速定位到存储数据的物理位置。索引可以是一个或多个列的组合,这意味着可以为表中的多个列创建多个索引。\n\n### 索引如何加快查询?\n\n索引的主要目的是为了提高查询效率。当数据库管理系统需要查找特定值时,它可以使用索引来快速定位到正确的行,而不需要扫描整个表。这大大减少了需要检查的行数量,从而加快了查询速度。\n\n例如,假设有一个包含用户信息的表,其中包含“用户名”和“电子邮件地址”字段。如果为“用户名”字段创建了一个索引,那么当执行一个查询,比如“查找所有以 @example.com 结尾的电子邮件地址”,数据库系统可以直接跳过那些不匹配的行,而不需要逐行扫描整个表。这样就大大提高了查询的速度。\n\n### 索引的代价\n\n虽然索引可以显著提高查询性能,但它们也会带来一些代价:\n\n1. **存储空间**:每个索引都需要额外的空间来存储其数据结构。因此,创建和维护索引会占用更多的存储空间。\n2. **更新开销**:插入、删除或修改表中的数据时,索引也需要相应地进行更新。这些操作通常比直接访问数据要慢得多,因为它们需要重新构建索引结构。\n3. **读取开销**:虽然索引可以加快查询速度,但在某些情况下,它们可能会增加读取操作的开销。这是因为每次查询时,数据库系统都需要从索引中找到对应的行,然后才能访问实际的数据。如果索引过于复杂或者太大,这可能会导致额外的读取开销。\n4. **内存使用**:在某些情况下,索引可能需要大量的内存资源来进行维护。例如,在某些情况下,数据库系统可能需要将索引缓存在内存中,以便快速访问。\n\n综上所述,索引是提高数据库查询性能的有效工具,但同时也需要注意其带来的成本。合理选择和管理索引对于优化数据库性能至关重要。", "elapsed": 137.1}, "SFT": {"answer": "索引就是给表里的列建一个“目录”,目录里存的是每行数据对应的物理位置,比如文件号或内存地址。查询时,数据库先在索引上定位到对应的数据块,再读那块数据,这样比扫描整个表快得多。\n\n代价主要两个:一是写入性能会下降,因为每次插入、更新、删除都要维护索引,而且索引越多,维护成本越高;二是空间浪费,每个索引要额外存元数据,比如叶子节点的值和指向数据块的指针。另外,如果索引列有重复值或者排序不唯一,查询可能返回多个结果,索引反而没用。本质上,索引是牺牲存储和写入效率,换查询速度。", "elapsed": 86.5}, "SFT + DPO": {"answer": "索引就是给表里的列建一个“目录”,目录里存的是每行数据对应的物理位置,比如主键索引就直接存行号。查询的时候,数据库会先查这个目录,定位到对应的数据块,再读那块数据,比着条件一个个比,找到符合条件的行,最后把结果放回。所以查询快,是因为省了“全表扫描”这一步。\n\n代价挺明显的:建索引要多占存储空间,而且每次写入都要更新索引,写操作变慢;删掉索引后,查询得走全表扫描,性能直接崩。另外,索引对空值和重复值不敏感,比如索引列全是100,那索引没用,反而占空间。所以建索引得看业务,比如经常按某个字段排序、频繁查询但很少更新的场景,建索引划算;如果更新频繁,或者字段有大量重复值,就别建了,直接全表扫描更省。本质上,索引是拿存储换查询速度,代价就是写性能和维护成本。", "elapsed": 23.2}}};
const QUESTIONS = Object.keys(DATA);
const STAGES = ['Base', 'SFT', 'SFT + DPO'];
let current = 0;
function renderTabs() {
const el = document.getElementById('tabs');
el.innerHTML = QUESTIONS.map((q, i) =>
`<button class="tab ${i===current?'active':''}" data-i="${i}">${i+1}</button>`).join('');
el.querySelectorAll('.tab').forEach(b => b.onclick = () => { current = +b.dataset.i; render(); });
}
function render() {
const q = QUESTIONS[current];
document.getElementById('qtitle').textContent = q;
document.getElementById('cols').innerHTML = STAGES.map(st => {
const d = DATA[q][st];
const cls = st === 'Base' ? 'base' : st === 'SFT' ? 'sft' : 'dpo';
return `<div class="col ${cls}">
<div class="stage"><span class="dot ${cls}"></span>${st}</div>
<div class="answer">${escapeHtml(d.answer)}</div>
<div class="time">耗时 ${d.elapsed}s</div>
</div>`;
}).join('');
renderTabs();
}
function escapeHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
render();
</script>
</body>
</html>