feat(appendix): 添加多个交互式演示组件,完善 AI/Infra 等章节内容
- 新增 Vibe Coding 全栈相关演示组件 (DeveloperSkillShift, FrontendTriad, BackendCore 等) - 新增 RAG 相关组件 (RAGPipeline, ChunkingStrategy, Retrieval 等) - 新增 Embedding & Vector 相关组件 (EmbeddingConcept, VectorSimilarity 等) - 新增 AI Native App 设计组件 (AINativeArch, PromptDesign 等) - 新增 Infrastructure as Code 组件 (IaCConcept, TerraformWorkflow 等) - 新增 DNS & HTTPS 演示组件 (DnsResolution, HttpsHandshake 等) - 新增 Model Finetuning 组件 (FinetuningPipeline 等) - 更新多个章节的 markdown 内容,集成交互式演示
This commit is contained in:
+333
@@ -0,0 +1,333 @@
|
||||
<template>
|
||||
<div class="finetuning-pipeline-demo">
|
||||
<div class="pipeline-header">
|
||||
<h4>微调流水线演示</h4>
|
||||
<p class="subtitle">点击每个阶段,了解微调的完整流程</p>
|
||||
</div>
|
||||
|
||||
<div class="pipeline-steps">
|
||||
<div
|
||||
v-for="(step, index) in steps"
|
||||
:key="step.id"
|
||||
class="pipeline-step"
|
||||
:class="{ active: activeStep === index, completed: index < activeStep }"
|
||||
@click="setStep(index)"
|
||||
>
|
||||
<div class="step-icon">{{ step.icon }}</div>
|
||||
<div class="step-label">{{ step.label }}</div>
|
||||
<div v-if="index < steps.length - 1" class="step-arrow">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-detail" v-if="activeStep >= 0">
|
||||
<div class="detail-title">
|
||||
{{ steps[activeStep].icon }} {{ steps[activeStep].label }}
|
||||
</div>
|
||||
<p class="detail-desc">{{ steps[activeStep].description }}</p>
|
||||
|
||||
<div class="detail-points">
|
||||
<div v-for="(point, i) in steps[activeStep].points" :key="i" class="point-item">
|
||||
<span class="point-bullet">{{ i + 1 }}</span>
|
||||
<span>{{ point }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-example" v-if="steps[activeStep].example">
|
||||
<div class="example-label">示例</div>
|
||||
<code>{{ steps[activeStep].example }}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pipeline-controls">
|
||||
<button class="ctrl-btn" :disabled="activeStep <= 0" @click="prevStep">上一步</button>
|
||||
<span class="step-indicator">{{ activeStep + 1 }} / {{ steps.length }}</span>
|
||||
<button class="ctrl-btn primary" :disabled="activeStep >= steps.length - 1" @click="nextStep">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const activeStep = ref(0)
|
||||
|
||||
const steps = [
|
||||
{
|
||||
id: 'base',
|
||||
icon: '🧠',
|
||||
label: '选择基座模型',
|
||||
description: '微调的第一步是选择一个合适的预训练基座模型。基座模型已经在海量数据上学习了通用的语言能力,我们要做的是在此基础上进行"专业化训练"。',
|
||||
points: [
|
||||
'根据任务需求选择模型规模(7B、13B、70B 等)',
|
||||
'考虑开源许可证(Apache 2.0、Llama 许可等)',
|
||||
'评估模型的基础能力是否匹配目标场景',
|
||||
'常见选择:Llama、Qwen、Mistral、DeepSeek 等'
|
||||
],
|
||||
example: 'model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-7B")'
|
||||
},
|
||||
{
|
||||
id: 'data',
|
||||
icon: '📊',
|
||||
label: '准备训练数据',
|
||||
description: '高质量的训练数据是微调成功的关键。数据的质量远比数量重要——1000 条精心标注的数据,往往胜过 10 万条噪声数据。',
|
||||
points: [
|
||||
'收集与目标任务相关的数据样本',
|
||||
'清洗数据:去重、过滤低质量内容',
|
||||
'格式化为模型要求的输入格式(如 instruction-response 对)',
|
||||
'划分训练集、验证集(通常 9:1)'
|
||||
],
|
||||
example: '{"instruction": "翻译成英文", "input": "你好世界", "output": "Hello World"}'
|
||||
},
|
||||
{
|
||||
id: 'train',
|
||||
icon: '⚙️',
|
||||
label: '执行微调训练',
|
||||
description: '使用准备好的数据对模型进行训练。现代微调通常采用参数高效方法(如 LoRA),只更新模型的一小部分参数,大幅降低计算成本。',
|
||||
points: [
|
||||
'配置训练超参数(学习率、批次大小、训练轮数)',
|
||||
'选择微调策略(全量微调 / LoRA / QLoRA)',
|
||||
'监控训练损失曲线,防止过拟合',
|
||||
'通常需要 1-4 个 GPU,训练数小时到数天'
|
||||
],
|
||||
example: 'trainer = SFTTrainer(model, train_dataset, peft_config=lora_config)'
|
||||
},
|
||||
{
|
||||
id: 'eval',
|
||||
icon: '📈',
|
||||
label: '评估与测试',
|
||||
description: '训练完成后,需要全面评估模型的表现。不仅要看自动化指标,更要进行人工评测,确保模型在真实场景中表现良好。',
|
||||
points: [
|
||||
'在验证集上计算损失和困惑度(Perplexity)',
|
||||
'使用任务特定指标(BLEU、ROUGE、准确率等)',
|
||||
'人工评测:流畅度、准确性、安全性',
|
||||
'与基座模型对比,确认微调带来了提升'
|
||||
],
|
||||
example: 'eval_results = trainer.evaluate(eval_dataset)'
|
||||
},
|
||||
{
|
||||
id: 'deploy',
|
||||
icon: '🚀',
|
||||
label: '部署上线',
|
||||
description: '将微调好的模型部署到生产环境,对外提供服务。部署前通常需要进行模型优化(量化、蒸馏等)以降低推理成本。',
|
||||
points: [
|
||||
'导出模型权重,合并 LoRA 适配器',
|
||||
'应用量化技术压缩模型体积',
|
||||
'选择部署方案(API 服务、边缘部署等)',
|
||||
'配置监控和日志,持续跟踪线上表现'
|
||||
],
|
||||
example: 'model.merge_and_unload().save_pretrained("my-finetuned-model")'
|
||||
}
|
||||
]
|
||||
|
||||
function setStep(index) {
|
||||
activeStep.value = index
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
if (activeStep.value > 0) activeStep.value--
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
if (activeStep.value < steps.length - 1) activeStep.value++
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.finetuning-pipeline-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin: 16px 0;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.pipeline-header h4 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 20px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.pipeline-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pipeline-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.pipeline-step.active .step-icon {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
background: var(--vp-c-brand-soft);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.pipeline-step.completed .step-icon {
|
||||
border-color: #10b981;
|
||||
background: #d1fae5;
|
||||
}
|
||||
|
||||
.step-label {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
max-width: 64px;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.pipeline-step.active .step-label {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.step-arrow {
|
||||
color: var(--vp-c-text-3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.step-detail {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.detail-points {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.point-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.point-bullet {
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-example {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.example-label {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.detail-example code {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-brand-1);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.pipeline-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.ctrl-btn {
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-2);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.ctrl-btn:hover:not(:disabled) {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.ctrl-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.ctrl-btn.primary {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: #fff;
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.ctrl-btn.primary:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.step-indicator {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,512 @@
|
||||
<template>
|
||||
<div class="lora-demo">
|
||||
<div class="demo-header">
|
||||
<h4>LoRA 低秩适配原理演示</h4>
|
||||
<p class="subtitle">理解 LoRA 如何用极少参数实现高效微调</p>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="tab-btn"
|
||||
:class="{ active: activeTab === tab.id }"
|
||||
@click="activeTab = tab.id"
|
||||
>{{ tab.label }}</button>
|
||||
</div>
|
||||
|
||||
<!-- 核心原理 -->
|
||||
<div v-if="activeTab === 'principle'" class="tab-content">
|
||||
<div class="matrix-visual">
|
||||
<div class="matrix-row">
|
||||
<div class="matrix-box frozen">
|
||||
<div class="matrix-label">原始权重 W</div>
|
||||
<div class="matrix-size">{{ matrixSize }}x{{ matrixSize }}</div>
|
||||
<div class="matrix-grid">
|
||||
<div v-for="i in 16" :key="i" class="cell frozen-cell"></div>
|
||||
</div>
|
||||
<div class="param-count">{{ (matrixSize * matrixSize).toLocaleString() }} 参数</div>
|
||||
<div class="status-badge frozen-badge">冻结不动</div>
|
||||
</div>
|
||||
|
||||
<div class="plus-sign">+</div>
|
||||
|
||||
<div class="matrix-box trainable">
|
||||
<div class="matrix-label">LoRA 适配器</div>
|
||||
<div class="lora-decompose">
|
||||
<div class="small-matrix a-matrix">
|
||||
<div class="sm-label">A</div>
|
||||
<div class="sm-size">{{ matrixSize }}x{{ loraRank }}</div>
|
||||
<div class="sm-grid">
|
||||
<div v-for="i in 8" :key="i" class="cell a-cell"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="multiply-sign">x</div>
|
||||
<div class="small-matrix b-matrix">
|
||||
<div class="sm-label">B</div>
|
||||
<div class="sm-size">{{ loraRank }}x{{ matrixSize }}</div>
|
||||
<div class="sm-grid">
|
||||
<div v-for="i in 8" :key="i" class="cell b-cell"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="param-count lora-count">{{ loraParams.toLocaleString() }} 参数</div>
|
||||
<div class="status-badge train-badge">可训练</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="savings-bar">
|
||||
<div class="savings-label">参数节省比例</div>
|
||||
<div class="bar-track">
|
||||
<div class="bar-fill" :style="{ width: savingsPercent + '%' }"></div>
|
||||
</div>
|
||||
<div class="savings-value">节省 {{ savingsPercent.toFixed(1) }}% 参数</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-control">
|
||||
<label>LoRA 秩 (Rank): <strong>{{ loraRank }}</strong></label>
|
||||
<input type="range" min="1" max="64" v-model.number="loraRank" />
|
||||
<div class="rank-hints">
|
||||
<span>秩越小 = 参数越少、训练越快</span>
|
||||
<span>秩越大 = 表达力越强、效果越好</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 直觉类比 -->
|
||||
<div v-if="activeTab === 'analogy'" class="tab-content">
|
||||
<div class="analogy-card">
|
||||
<div class="analogy-icon">🎨</div>
|
||||
<div class="analogy-text">
|
||||
<p><strong>想象你有一幅巨大的油画(预训练模型)。</strong></p>
|
||||
<p>传统微调就像把整幅画重新画一遍——费时费力,还可能破坏原作的精髓。</p>
|
||||
<p>而 LoRA 的做法是:<strong>在原画上覆盖一层薄薄的透明贴纸</strong>,只在贴纸上做修改。原画完好无损,贴纸又轻又薄,随时可以换。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-table">
|
||||
<div class="comp-row header">
|
||||
<div class="comp-cell">对比维度</div>
|
||||
<div class="comp-cell">全量微调</div>
|
||||
<div class="comp-cell highlight">LoRA 微调</div>
|
||||
</div>
|
||||
<div v-for="row in comparisonRows" :key="row.dim" class="comp-row">
|
||||
<div class="comp-cell dim">{{ row.dim }}</div>
|
||||
<div class="comp-cell">{{ row.full }}</div>
|
||||
<div class="comp-cell highlight">{{ row.lora }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实际应用 -->
|
||||
<div v-if="activeTab === 'usage'" class="tab-content">
|
||||
<div class="usage-steps">
|
||||
<div v-for="(step, i) in usageSteps" :key="i" class="usage-step">
|
||||
<div class="usage-num">{{ i + 1 }}</div>
|
||||
<div class="usage-body">
|
||||
<div class="usage-title">{{ step.title }}</div>
|
||||
<div class="usage-code">
|
||||
<code>{{ step.code }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const activeTab = ref('principle')
|
||||
const matrixSize = ref(4096)
|
||||
const loraRank = ref(8)
|
||||
|
||||
const tabs = [
|
||||
{ id: 'principle', label: '核心原理' },
|
||||
{ id: 'analogy', label: '直觉类比' },
|
||||
{ id: 'usage', label: '实际应用' }
|
||||
]
|
||||
|
||||
const loraParams = computed(() => {
|
||||
return matrixSize.value * loraRank.value + loraRank.value * matrixSize.value
|
||||
})
|
||||
|
||||
const fullParams = computed(() => matrixSize.value * matrixSize.value)
|
||||
|
||||
const savingsPercent = computed(() => {
|
||||
return ((1 - loraParams.value / fullParams.value) * 100)
|
||||
})
|
||||
|
||||
const comparisonRows = [
|
||||
{ dim: '训练参数量', full: '100%(数十亿)', lora: '0.1%~1%(数百万)' },
|
||||
{ dim: '显存需求', full: '4x A100 80GB', lora: '1x RTX 4090 24GB' },
|
||||
{ dim: '训练时间', full: '数天~数周', lora: '数小时~1天' },
|
||||
{ dim: '存储开销', full: '完整模型副本(~14GB)', lora: '适配器文件(~几十MB)' },
|
||||
{ dim: '多任务切换', full: '需要多个完整模型', lora: '共享基座 + 切换适配器' },
|
||||
{ dim: '训练效果', full: '理论上限最高', lora: '接近全量微调(90%+)' }
|
||||
]
|
||||
|
||||
const usageSteps = [
|
||||
{
|
||||
title: '配置 LoRA 参数',
|
||||
code: `lora_config = LoraConfig(\n r=8, # 秩\n lora_alpha=16, # 缩放因子\n target_modules=["q_proj", "v_proj"],\n lora_dropout=0.05\n)`
|
||||
},
|
||||
{
|
||||
title: '应用到模型',
|
||||
code: `model = get_peft_model(base_model, lora_config)\nmodel.print_trainable_parameters()\n# 可训练参数: 4,194,304 / 6,738,415,616 (0.06%)`
|
||||
},
|
||||
{
|
||||
title: '训练完成后合并',
|
||||
code: `merged_model = model.merge_and_unload()\nmerged_model.save_pretrained("my-model")`
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.lora-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin: 16px 0;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.demo-header h4 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 16px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-2);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: #fff;
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.matrix-visual {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.matrix-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.matrix-box {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.matrix-box.frozen {
|
||||
border-color: #94a3b8;
|
||||
}
|
||||
|
||||
.matrix-box.trainable {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.matrix-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.matrix-size {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.matrix-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 2px;
|
||||
margin: 0 auto 8px;
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.frozen-cell {
|
||||
background: #cbd5e1;
|
||||
}
|
||||
|
||||
.param-count {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.lora-count {
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.frozen-badge {
|
||||
background: #e2e8f0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.train-badge {
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.plus-sign, .multiply-sign {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.lora-decompose {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.small-matrix {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sm-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.sm-size {
|
||||
font-size: 10px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.sm-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 2px;
|
||||
margin: 4px auto;
|
||||
max-width: 40px;
|
||||
}
|
||||
|
||||
.a-cell {
|
||||
background: #818cf8;
|
||||
}
|
||||
|
||||
.b-cell {
|
||||
background: #f472b6;
|
||||
}
|
||||
|
||||
.savings-bar {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.savings-label {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.bar-track {
|
||||
height: 8px;
|
||||
background: var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--vp-c-brand-1), #10b981);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.savings-value {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.rank-control {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.rank-control label {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.rank-control input[type="range"] {
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.rank-hints {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.analogy-card {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 4px solid var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.analogy-icon {
|
||||
font-size: 36px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.analogy-text p {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.comparison-table {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.comp-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.2fr 1.2fr;
|
||||
}
|
||||
|
||||
.comp-row.header {
|
||||
background: var(--vp-c-bg-alt);
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.comp-cell {
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.comp-cell.dim {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.comp-cell.highlight {
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.usage-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.usage-step {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.usage-num {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--vp-c-brand-1);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.usage-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.usage-code {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.usage-code code {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-brand-1);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<div class="quantization-demo">
|
||||
<div class="demo-header">
|
||||
<h4>模型量化演示</h4>
|
||||
<p class="subtitle">拖动滑块,直观感受不同精度下的模型体积、速度与质量变化</p>
|
||||
</div>
|
||||
|
||||
<div class="precision-selector">
|
||||
<div
|
||||
v-for="(p, i) in precisions"
|
||||
:key="p.id"
|
||||
class="precision-card"
|
||||
:class="{ active: activePrecision === i }"
|
||||
@click="activePrecision = i"
|
||||
>
|
||||
<div class="prec-badge" :style="{ background: p.color }">{{ p.label }}</div>
|
||||
<div class="prec-bits">{{ p.bits }} bit</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon">💾</div>
|
||||
<div class="metric-label">模型体积</div>
|
||||
<div class="metric-bar-wrap">
|
||||
<div class="metric-bar" :style="{ width: currentPrecision.sizePercent + '%', background: currentPrecision.color }"></div>
|
||||
</div>
|
||||
<div class="metric-value">{{ currentPrecision.size }}</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon">⚡</div>
|
||||
<div class="metric-label">推理速度</div>
|
||||
<div class="metric-bar-wrap">
|
||||
<div class="metric-bar" :style="{ width: currentPrecision.speedPercent + '%', background: '#10b981' }"></div>
|
||||
</div>
|
||||
<div class="metric-value">{{ currentPrecision.speed }}</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon">🎯</div>
|
||||
<div class="metric-label">输出质量</div>
|
||||
<div class="metric-bar-wrap">
|
||||
<div class="metric-bar" :style="{ width: currentPrecision.qualityPercent + '%', background: '#818cf8' }"></div>
|
||||
</div>
|
||||
<div class="metric-value">{{ currentPrecision.quality }}</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon">🖥️</div>
|
||||
<div class="metric-label">显存需求</div>
|
||||
<div class="metric-bar-wrap">
|
||||
<div class="metric-bar" :style="{ width: currentPrecision.vramPercent + '%', background: '#f59e0b' }"></div>
|
||||
</div>
|
||||
<div class="metric-value">{{ currentPrecision.vram }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-title">{{ currentPrecision.label }} 详解</div>
|
||||
<p class="detail-desc">{{ currentPrecision.description }}</p>
|
||||
|
||||
<div class="bit-visual">
|
||||
<div class="bit-label">单个参数存储示意</div>
|
||||
<div class="bit-row">
|
||||
<div
|
||||
v-for="i in currentPrecision.bits"
|
||||
:key="i"
|
||||
class="bit-cell"
|
||||
:style="{ background: currentPrecision.color }"
|
||||
>{{ i % 2 === 0 ? '1' : '0' }}</div>
|
||||
</div>
|
||||
<div class="bit-info">每个参数占用 {{ currentPrecision.bits }} 位 = {{ currentPrecision.bytes }} 字节</div>
|
||||
</div>
|
||||
|
||||
<div class="use-case">
|
||||
<span class="use-label">适用场景:</span>
|
||||
<span>{{ currentPrecision.useCase }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const activePrecision = ref(0)
|
||||
|
||||
const precisions = [
|
||||
{
|
||||
id: 'fp32',
|
||||
label: 'FP32',
|
||||
bits: 32,
|
||||
bytes: 4,
|
||||
color: '#ef4444',
|
||||
size: '~28 GB (7B 模型)',
|
||||
sizePercent: 100,
|
||||
speed: '1x (基准)',
|
||||
speedPercent: 25,
|
||||
quality: '100% (无损)',
|
||||
qualityPercent: 100,
|
||||
vram: '~32 GB',
|
||||
vramPercent: 100,
|
||||
description: 'FP32(32位浮点数)是模型训练时的默认精度。每个参数用 32 位存储,精度最高但体积最大。通常只在训练阶段使用,推理时很少直接使用 FP32。',
|
||||
useCase: '模型训练、科研实验、精度敏感的任务'
|
||||
},
|
||||
{
|
||||
id: 'fp16',
|
||||
label: 'FP16',
|
||||
bits: 16,
|
||||
bytes: 2,
|
||||
color: '#f59e0b',
|
||||
size: '~14 GB (7B 模型)',
|
||||
sizePercent: 50,
|
||||
speed: '2x',
|
||||
speedPercent: 50,
|
||||
quality: '~99.5%',
|
||||
qualityPercent: 99,
|
||||
vram: '~16 GB',
|
||||
vramPercent: 50,
|
||||
description: 'FP16(16位浮点数)将精度减半,模型体积直接缩小一半。在绝大多数场景下,FP16 的输出质量与 FP32 几乎无差别,是目前最主流的推理精度。',
|
||||
useCase: '标准推理部署、GPU 服务器、大多数生产环境'
|
||||
},
|
||||
{
|
||||
id: 'int8',
|
||||
label: 'INT8',
|
||||
bits: 8,
|
||||
bytes: 1,
|
||||
color: '#10b981',
|
||||
size: '~7 GB (7B 模型)',
|
||||
sizePercent: 25,
|
||||
speed: '3-4x',
|
||||
speedPercent: 75,
|
||||
quality: '~98%',
|
||||
qualityPercent: 96,
|
||||
vram: '~8 GB',
|
||||
vramPercent: 25,
|
||||
description: 'INT8(8位整数)量化将浮点数映射为整数,体积仅为 FP32 的四分之一。质量损失很小,但推理速度显著提升。适合在消费级 GPU 上运行大模型。',
|
||||
useCase: '消费级 GPU 部署(RTX 4090)、成本敏感场景'
|
||||
},
|
||||
{
|
||||
id: 'int4',
|
||||
label: 'INT4',
|
||||
bits: 4,
|
||||
bytes: 0.5,
|
||||
color: '#818cf8',
|
||||
size: '~3.5 GB (7B 模型)',
|
||||
sizePercent: 12.5,
|
||||
speed: '5-6x',
|
||||
speedPercent: 90,
|
||||
quality: '~93-95%',
|
||||
qualityPercent: 90,
|
||||
vram: '~4 GB',
|
||||
vramPercent: 12.5,
|
||||
description: 'INT4(4位整数)是目前最激进的量化方案。模型体积压缩到 FP32 的八分之一,甚至可以在笔记本电脑上运行 7B 模型。质量有一定损失,但对于大多数应用仍然可用。',
|
||||
useCase: '笔记本/手机端部署、边缘计算、离线场景'
|
||||
}
|
||||
]
|
||||
|
||||
const currentPrecision = computed(() => precisions[activePrecision.value])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.quantization-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin: 16px 0;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.demo-header h4 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 20px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.precision-selector {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.precision-card {
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.precision-card.active {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.prec-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.prec-bits {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.metrics-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 18px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metric-bar-wrap {
|
||||
height: 6px;
|
||||
background: var(--vp-c-divider);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.metric-bar {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.bit-visual {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.bit-label {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.bit-row {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.bit-cell {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.bit-info {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.use-case {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
padding: 8px 12px;
|
||||
background: var(--vp-c-brand-soft);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.use-label {
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<div class="model-serving-demo">
|
||||
<div class="demo-header">
|
||||
<h4>模型服务架构演示</h4>
|
||||
<p class="subtitle">点击不同部署方案,对比其特点与适用场景</p>
|
||||
</div>
|
||||
|
||||
<div class="serving-options">
|
||||
<div
|
||||
v-for="(opt, i) in options"
|
||||
:key="opt.id"
|
||||
class="option-card"
|
||||
:class="{ active: activeOption === i }"
|
||||
@click="activeOption = i"
|
||||
>
|
||||
<div class="opt-icon">{{ opt.icon }}</div>
|
||||
<div class="opt-name">{{ opt.name }}</div>
|
||||
<div class="opt-brief">{{ opt.brief }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option-detail" v-if="currentOption">
|
||||
<div class="detail-header">
|
||||
<span class="detail-icon">{{ currentOption.icon }}</span>
|
||||
<span class="detail-name">{{ currentOption.name }}</span>
|
||||
</div>
|
||||
<p class="detail-desc">{{ currentOption.description }}</p>
|
||||
|
||||
<div class="arch-flow">
|
||||
<div class="flow-label">架构流程</div>
|
||||
<div class="flow-steps">
|
||||
<div v-for="(node, i) in currentOption.flow" :key="i" class="flow-node">
|
||||
<div class="node-box">{{ node }}</div>
|
||||
<div v-if="i < currentOption.flow.length - 1" class="flow-arrow-h">→</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="specs-grid">
|
||||
<div v-for="spec in currentOption.specs" :key="spec.label" class="spec-item">
|
||||
<div class="spec-label">{{ spec.label }}</div>
|
||||
<div class="spec-value">{{ spec.value }}</div>
|
||||
<div class="spec-bar-wrap">
|
||||
<div class="spec-bar" :style="{ width: spec.score + '%', background: spec.color }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tools-section">
|
||||
<div class="tools-label">常用工具</div>
|
||||
<div class="tools-list">
|
||||
<span v-for="tool in currentOption.tools" :key="tool" class="tool-tag">{{ tool }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const activeOption = ref(0)
|
||||
|
||||
const options = [
|
||||
{
|
||||
id: 'api',
|
||||
icon: '🌐',
|
||||
name: 'API 服务',
|
||||
brief: '最常见的在线部署方式',
|
||||
description: '将模型封装为 RESTful API 或 gRPC 服务,通过 HTTP 请求调用。适合需要实时响应的在线应用,如聊天机器人、智能客服、内容生成等。是目前最主流的部署方式。',
|
||||
flow: ['客户端请求', '负载均衡', '推理服务器', 'GPU 推理', '返回结果'],
|
||||
specs: [
|
||||
{ label: '响应延迟', value: '100ms - 2s', score: 70, color: '#10b981' },
|
||||
{ label: '并发能力', value: '高(可水平扩展)', score: 85, color: '#818cf8' },
|
||||
{ label: '部署成本', value: '中高(需 GPU 服务器)', score: 50, color: '#f59e0b' },
|
||||
{ label: '运维复杂度', value: '中等', score: 55, color: '#ef4444' }
|
||||
],
|
||||
tools: ['vLLM', 'TGI', 'Triton', 'FastAPI', 'Ollama']
|
||||
},
|
||||
{
|
||||
id: 'edge',
|
||||
icon: '📱',
|
||||
name: '边缘部署',
|
||||
brief: '在终端设备上本地运行',
|
||||
description: '将量化后的模型部署到手机、笔记本、嵌入式设备等终端上,无需网络连接即可运行。适合隐私敏感、离线场景或需要极低延迟的应用。',
|
||||
flow: ['模型量化', '格式转换', '设备加载', '本地推理', '即时输出'],
|
||||
specs: [
|
||||
{ label: '响应延迟', value: '50ms - 5s', score: 60, color: '#10b981' },
|
||||
{ label: '并发能力', value: '低(单设备)', score: 20, color: '#818cf8' },
|
||||
{ label: '部署成本', value: '低(无服务器费用)', score: 90, color: '#f59e0b' },
|
||||
{ label: '运维复杂度', value: '低', score: 85, color: '#ef4444' }
|
||||
],
|
||||
tools: ['llama.cpp', 'MLC LLM', 'ONNX Runtime', 'MediaPipe']
|
||||
},
|
||||
{
|
||||
id: 'batch',
|
||||
icon: '📦',
|
||||
name: '批量处理',
|
||||
brief: '离线批量推理大量数据',
|
||||
description: '将大量请求收集后统一处理,不要求实时响应。适合数据标注、文档摘要、批量翻译等离线任务。通过批处理可以最大化 GPU 利用率,显著降低单条推理成本。',
|
||||
flow: ['数据队列', '批量收集', 'GPU 批推理', '结果存储', '异步通知'],
|
||||
specs: [
|
||||
{ label: '响应延迟', value: '分钟~小时级', score: 20, color: '#10b981' },
|
||||
{ label: '吞吐量', value: '极高(批处理优化)', score: 95, color: '#818cf8' },
|
||||
{ label: '部署成本', value: '低(GPU 利用率高)', score: 85, color: '#f59e0b' },
|
||||
{ label: '运维复杂度', value: '中等', score: 55, color: '#ef4444' }
|
||||
],
|
||||
tools: ['Ray Serve', 'Spark', 'Celery', 'AWS Batch']
|
||||
}
|
||||
]
|
||||
|
||||
const currentOption = computed(() => options[activeOption.value])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.model-serving-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin: 16px 0;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.demo-header h4 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 20px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.serving-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.serving-options {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.option-card {
|
||||
background: var(--vp-c-bg);
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.option-card.active {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
background: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
.opt-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.opt-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.opt-brief {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.option-detail {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.detail-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.arch-flow {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.flow-label {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.flow-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flow-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.node-box {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.flow-arrow-h {
|
||||
color: var(--vp-c-text-3);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.specs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.specs-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.spec-item {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.spec-label {
|
||||
font-size: 11px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.spec-value {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.spec-bar-wrap {
|
||||
height: 4px;
|
||||
background: var(--vp-c-divider);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.spec-bar {
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.tools-section {
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.tools-label {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tools-list {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tool-tag {
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div class="training-data-demo">
|
||||
<div class="demo-header">
|
||||
<h4>训练数据格式演示</h4>
|
||||
<p class="subtitle">切换不同格式,了解微调数据的组织方式</p>
|
||||
</div>
|
||||
|
||||
<div class="format-tabs">
|
||||
<button
|
||||
v-for="fmt in formats"
|
||||
:key="fmt.id"
|
||||
class="fmt-btn"
|
||||
:class="{ active: activeFormat === fmt.id }"
|
||||
@click="activeFormat = fmt.id"
|
||||
>
|
||||
<span class="fmt-icon">{{ fmt.icon }}</span>
|
||||
<span>{{ fmt.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="format-detail">
|
||||
<div class="format-info">
|
||||
<div class="info-title">{{ currentFormat.label }}</div>
|
||||
<p class="info-desc">{{ currentFormat.description }}</p>
|
||||
<div class="info-tags">
|
||||
<span class="tag" v-for="tag in currentFormat.tags" :key="tag">{{ tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="data-preview">
|
||||
<div class="preview-header">
|
||||
<span class="preview-label">数据样例</span>
|
||||
<button class="switch-btn" @click="nextExample">
|
||||
换一条 ↻
|
||||
</button>
|
||||
</div>
|
||||
<div class="json-block">
|
||||
<div v-for="(line, i) in currentExample" :key="i" class="json-line">
|
||||
<span class="json-key" v-if="line.key">{{ line.key }}</span>
|
||||
<span class="json-colon" v-if="line.key">: </span>
|
||||
<span :class="'json-value ' + (line.type || '')">{{ line.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quality-tips">
|
||||
<div class="tips-title">数据质量要点</div>
|
||||
<div class="tips-list">
|
||||
<div v-for="(tip, i) in currentFormat.tips" :key="i" class="tip-item">
|
||||
<span class="tip-check">✓</span>
|
||||
<span>{{ tip }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const activeFormat = ref('instruction')
|
||||
const exampleIndex = ref(0)
|
||||
|
||||
const formats = [
|
||||
{
|
||||
id: 'instruction',
|
||||
icon: '📝',
|
||||
label: '指令跟随',
|
||||
description: '最常见的微调数据格式。每条数据包含一个指令(instruction)、可选的输入(input)和期望的输出(output)。适合训练通用助手类模型。',
|
||||
tags: ['通用助手', 'ChatGPT 风格', '最常用'],
|
||||
tips: [
|
||||
'指令要清晰明确,避免歧义',
|
||||
'输出要完整、准确、格式规范',
|
||||
'覆盖多种任务类型(翻译、摘要、问答等)',
|
||||
'数据量建议:1,000 ~ 50,000 条'
|
||||
],
|
||||
examples: [
|
||||
[
|
||||
{ key: '"instruction"', value: '"请将以下中文翻译成英文"', type: 'string' },
|
||||
{ key: '"input"', value: '"人工智能正在改变世界"', type: 'string' },
|
||||
{ key: '"output"', value: '"AI is changing the world"', type: 'string' }
|
||||
],
|
||||
[
|
||||
{ key: '"instruction"', value: '"用一句话总结以下段落"', type: 'string' },
|
||||
{ key: '"input"', value: '"深度学习是机器学习的一个分支..."', type: 'string' },
|
||||
{ key: '"output"', value: '"深度学习通过多层神经网络自动学习数据特征"', type: 'string' }
|
||||
],
|
||||
[
|
||||
{ key: '"instruction"', value: '"解释什么是 API"', type: 'string' },
|
||||
{ key: '"input"', value: '""', type: 'string' },
|
||||
{ key: '"output"', value: '"API 是应用程序编程接口,它定义了..."', type: 'string' }
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'conversation',
|
||||
icon: '💬',
|
||||
label: '多轮对话',
|
||||
description: '模拟真实的多轮对话场景。每条数据包含一组对话消息,包括系统提示、用户消息和助手回复。适合训练聊天机器人。',
|
||||
tags: ['聊天机器人', '多轮交互', '上下文理解'],
|
||||
tips: [
|
||||
'对话要自然流畅,符合真实交互模式',
|
||||
'保持角色一致性(系统提示贯穿始终)',
|
||||
'包含上下文引用和追问场景',
|
||||
'数据量建议:5,000 ~ 100,000 条对话'
|
||||
],
|
||||
examples: [
|
||||
[
|
||||
{ key: '"messages"', value: '[', type: 'bracket' },
|
||||
{ key: ' {"role"', value: '"system", "content": "你是一个编程助手"}', type: 'string' },
|
||||
{ key: ' {"role"', value: '"user", "content": "Python 怎么读取文件?"}', type: 'string' },
|
||||
{ key: ' {"role"', value: '"assistant", "content": "使用 open() 函数..."}', type: 'string' },
|
||||
{ key: '', value: ']', type: 'bracket' }
|
||||
],
|
||||
[
|
||||
{ key: '"messages"', value: '[', type: 'bracket' },
|
||||
{ key: ' {"role"', value: '"system", "content": "你是一个医疗顾问"}', type: 'string' },
|
||||
{ key: ' {"role"', value: '"user", "content": "感冒了怎么办?"}', type: 'string' },
|
||||
{ key: ' {"role"', value: '"assistant", "content": "建议多休息多喝水..."}', type: 'string' },
|
||||
{ key: ' {"role"', value: '"user", "content": "需要吃药吗?"}', type: 'string' },
|
||||
{ key: ' {"role"', value: '"assistant", "content": "如果症状较轻..."}', type: 'string' },
|
||||
{ key: '', value: ']', type: 'bracket' }
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'classification',
|
||||
icon: '🏷️',
|
||||
label: '分类标注',
|
||||
description: '用于训练文本分类任务。每条数据包含输入文本和对应的类别标签。适合情感分析、意图识别、内容审核等场景。',
|
||||
tags: ['情感分析', '意图识别', '内容审核'],
|
||||
tips: [
|
||||
'类别标签要统一规范,避免拼写差异',
|
||||
'各类别样本数量尽量均衡',
|
||||
'包含边界案例和易混淆样本',
|
||||
'数据量建议:每个类别至少 100 条'
|
||||
],
|
||||
examples: [
|
||||
[
|
||||
{ key: '"text"', value: '"这家餐厅的菜品非常好吃,服务也很周到"', type: 'string' },
|
||||
{ key: '"label"', value: '"positive"', type: 'label' }
|
||||
],
|
||||
[
|
||||
{ key: '"text"', value: '"等了一个小时还没上菜,太失望了"', type: 'string' },
|
||||
{ key: '"label"', value: '"negative"', type: 'label' }
|
||||
],
|
||||
[
|
||||
{ key: '"text"', value: '"餐厅环境一般,价格中等"', type: 'string' },
|
||||
{ key: '"label"', value: '"neutral"', type: 'label' }
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const currentFormat = computed(() => {
|
||||
return formats.find(f => f.id === activeFormat.value)
|
||||
})
|
||||
|
||||
const currentExample = computed(() => {
|
||||
const examples = currentFormat.value.examples
|
||||
return examples[exampleIndex.value % examples.length]
|
||||
})
|
||||
|
||||
function nextExample() {
|
||||
exampleIndex.value++
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.training-data-demo {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin: 16px 0;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.demo-header h4 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 16px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.format-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fmt-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-2);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.fmt-btn.active {
|
||||
background: var(--vp-c-brand-1);
|
||||
color: #fff;
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.fmt-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.format-detail {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.format-info {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.info-desc {
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.info-tags {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.data-preview {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.preview-label {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.switch-btn:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.json-block {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
.json-line {
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.json-key {
|
||||
color: #818cf8;
|
||||
}
|
||||
|
||||
.json-colon {
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.json-value.string {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.json-value.label {
|
||||
color: #f59e0b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.json-value.bracket {
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.quality-tips {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border-left: 3px solid #10b981;
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tips-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.tip-check {
|
||||
color: #10b981;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user