feat(chart): 支持 MoE 模型识别与可视化增强
Browse files- 新增 `_isMoE` 标记支持,将 MoE 模型归类为独立系列并展示为星形标记
- 图表符号大小根据数据量动态调整,提高可读性和交互体验
- 增加对 y 轴单值居中处理逻辑,避免零值被误设为 1
- 引入模型类型排序机制,使图例和系列按照预定义顺序显示
- 添加 `selectedDataNameChart` 用于图表数据源筛选,分离控制维度
- 更新默认自动显示系列配置,支持通配符 "*" 显示所有系列
- 调整图表容器宽度,优化布局适配
- src/components/Chart.vue +92 -26
- src/composables/useLeaderboardData.js +47 -19
- src/views/Leaderboard.vue +65 -30
src/components/Chart.vue
CHANGED
|
@@ -6,7 +6,7 @@ import * as echarts from 'echarts'
|
|
| 6 |
const chartRef = ref(null)
|
| 7 |
let chart = null
|
| 8 |
|
| 9 |
-
const { leaderboard,
|
| 10 |
|
| 11 |
// 接受一个可选 prop:autoShowSeries(数组),用于指定哪些模型类型在图表加载时自动显示。
|
| 12 |
// 例如:<Chart :autoShowSeries="['gemma-3','Hunyuan']" />
|
|
@@ -19,12 +19,15 @@ function buildSeriesFromLeaderboard(rows = []) {
|
|
| 19 |
const groups = {}
|
| 20 |
rows.forEach(r => {
|
| 21 |
// 按 model_type 分组;如果 model_type 未填则标记为 未知
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
| 23 |
if (!groups[seriesName]) groups[seriesName] = []
|
| 24 |
// 数据原本单位为 TFLOPs,转换为 FLOPs(1 TFLOP = 1e12 FLOPs)用于对数尺度显示
|
| 25 |
const xRaw = parseFloat(r.BF16_TFLOPs) || 0
|
| 26 |
const x = xRaw > 0 ? xRaw * 1e12 : 0
|
| 27 |
-
const y = parseFloat(r.
|
| 28 |
const rank = Number(r.rank) || 0
|
| 29 |
groups[seriesName].push({ x, y, rank })
|
| 30 |
})
|
|
@@ -35,7 +38,10 @@ function buildSeriesFromLeaderboard(rows = []) {
|
|
| 35 |
.filter(p => Number.isFinite(p.x) && p.x > 0 && Number.isFinite(p.y))
|
| 36 |
.sort((a, b) => a.rank - b.rank)
|
| 37 |
.map(d => [d.x, d.y])
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
| 39 |
})
|
| 40 |
}
|
| 41 |
|
|
@@ -74,9 +80,17 @@ function getChartOption(series) {
|
|
| 74 |
if (yMin !== undefined && yMax !== undefined) {
|
| 75 |
if (yMin === yMax) {
|
| 76 |
// small symmetric padding
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
} else {
|
| 81 |
const pad = (yMax - yMin) * 0.08
|
| 82 |
yMin = yMin - pad
|
|
@@ -93,27 +107,79 @@ function getChartOption(series) {
|
|
| 93 |
return s.split('').map(ch => map[ch] || ch).join('')
|
| 94 |
}
|
| 95 |
// 可循环使用的视觉样式(颜色/符号/线型)
|
| 96 |
-
|
| 97 |
-
const
|
| 98 |
-
const lineStyles = ['solid','solid','solid','solid','dash','solid','solid'
|
| 99 |
|
| 100 |
-
//
|
| 101 |
-
const
|
| 102 |
-
const
|
| 103 |
-
return Object.assign({}, s, {
|
| 104 |
-
lineStyle: { width: 2, type: lineStyles[idx % lineStyles.length], color },
|
| 105 |
-
itemStyle: { color },
|
| 106 |
-
symbol: symbols[idx % symbols.length],
|
| 107 |
-
emphasis: { focus: 'series' }
|
| 108 |
-
})
|
| 109 |
})
|
| 110 |
|
| 111 |
// 构建 legend.selected 映射:只有 props.autoShowSeries 中的系列会默认显示,其他需手动点击 legend 打开
|
| 112 |
const legendSelected = {}
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
const name = String(s.name)
|
| 115 |
legendSelected[name] = Array.isArray(props.autoShowSeries) && props.autoShowSeries.includes(name)
|
| 116 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
return {
|
| 119 |
color: colors,
|
|
@@ -147,7 +213,7 @@ function getChartOption(series) {
|
|
| 147 |
itemHeight: 10,
|
| 148 |
textStyle: { color: '#cfd8dc' },
|
| 149 |
tooltip: { show: true }
|
| 150 |
-
|
| 151 |
grid: { left: '6%', right: '20%', bottom: '8%', containLabel: true },
|
| 152 |
xAxis: {
|
| 153 |
type: 'log',
|
|
@@ -178,7 +244,7 @@ function getChartOption(series) {
|
|
| 178 |
min: yMin,
|
| 179 |
max: yMax
|
| 180 |
},
|
| 181 |
-
series:
|
| 182 |
}
|
| 183 |
}
|
| 184 |
|
|
@@ -187,8 +253,8 @@ let resizeHandler = null
|
|
| 187 |
function renderChart() {
|
| 188 |
if (!chart) return
|
| 189 |
let rows = leaderboard.value || []
|
| 190 |
-
// 根据
|
| 191 |
-
const sel =
|
| 192 |
if (sel && sel !== 'all') {
|
| 193 |
rows = rows.filter(r => String(r.data_name ?? '') === sel)
|
| 194 |
}
|
|
@@ -213,7 +279,7 @@ watch(leaderboard, () => {
|
|
| 213 |
}, { deep: true })
|
| 214 |
|
| 215 |
// 当所选数据集变化时也要重新渲染图表
|
| 216 |
-
watch(
|
| 217 |
renderChart()
|
| 218 |
})
|
| 219 |
|
|
@@ -224,7 +290,7 @@ onBeforeUnmount(() => {
|
|
| 224 |
</script>
|
| 225 |
|
| 226 |
<template>
|
| 227 |
-
<div ref="chartRef" style="width:
|
| 228 |
</template>
|
| 229 |
|
| 230 |
<style scoped></style>
|
|
|
|
| 6 |
const chartRef = ref(null)
|
| 7 |
let chart = null
|
| 8 |
|
| 9 |
+
const { leaderboard, selectedDataNameChart, modelTypeGroups } = useLeaderboardData()
|
| 10 |
|
| 11 |
// 接受一个可选 prop:autoShowSeries(数组),用于指定哪些模型类型在图表加载时自动显示。
|
| 12 |
// 例如:<Chart :autoShowSeries="['gemma-3','Hunyuan']" />
|
|
|
|
| 19 |
const groups = {}
|
| 20 |
rows.forEach(r => {
|
| 21 |
// 按 model_type 分组;如果 model_type 未填则标记为 未知
|
| 22 |
+
// 优化:如果行上有 _isMoE 标记,则把其归为 "<model_type> (MoE)" 系列,且保留 model_series_base
|
| 23 |
+
const baseType = r.model_type || '未知'
|
| 24 |
+
const isMoE = !!r._isMoE
|
| 25 |
+
const seriesName = isMoE ? `${baseType} (MoE)` : baseType
|
| 26 |
if (!groups[seriesName]) groups[seriesName] = []
|
| 27 |
// 数据原本单位为 TFLOPs,转换为 FLOPs(1 TFLOP = 1e12 FLOPs)用于对数尺度显示
|
| 28 |
const xRaw = parseFloat(r.BF16_TFLOPs) || 0
|
| 29 |
const x = xRaw > 0 ? xRaw * 1e12 : 0
|
| 30 |
+
const y = parseFloat(r.ic) || 0
|
| 31 |
const rank = Number(r.rank) || 0
|
| 32 |
groups[seriesName].push({ x, y, rank })
|
| 33 |
})
|
|
|
|
| 38 |
.filter(p => Number.isFinite(p.x) && p.x > 0 && Number.isFinite(p.y))
|
| 39 |
.sort((a, b) => a.rank - b.rank)
|
| 40 |
.map(d => [d.x, d.y])
|
| 41 |
+
// 根据该系列的样本点数量适度放大 symbolSize,避免点太小难以点击
|
| 42 |
+
const count = groups[name].length || 1
|
| 43 |
+
const symbolSize = Math.min(18, 8 + Math.sqrt(count) * 1.6)
|
| 44 |
+
return { name, rawCount: count, type: 'line', showSymbol: true, symbolSize, data }
|
| 45 |
})
|
| 46 |
}
|
| 47 |
|
|
|
|
| 80 |
if (yMin !== undefined && yMax !== undefined) {
|
| 81 |
if (yMin === yMax) {
|
| 82 |
// small symmetric padding
|
| 83 |
+
// 如果只有单一数值,使用该值作为中心;但若该值为 0,则不要把区间提升到以 1 为中心(之前用 `yMin || 1` 会导致 0 -> 1)
|
| 84 |
+
const v = (yMin !== undefined ? yMin : 1)
|
| 85 |
+
if (v === 0) {
|
| 86 |
+
// 为 0 时给一个小的对称 padding(之后如果原始数据全为正,会被下限为 0)
|
| 87 |
+
const pad = 0.05
|
| 88 |
+
yMin = -pad
|
| 89 |
+
yMax = pad
|
| 90 |
+
} else {
|
| 91 |
+
yMin = v - Math.abs(v) * 0.05
|
| 92 |
+
yMax = v + Math.abs(v) * 0.05
|
| 93 |
+
}
|
| 94 |
} else {
|
| 95 |
const pad = (yMax - yMin) * 0.08
|
| 96 |
yMin = yMin - pad
|
|
|
|
| 107 |
return s.split('').map(ch => map[ch] || ch).join('')
|
| 108 |
}
|
| 109 |
// 可循环使用的视觉样式(颜色/符号/线型)
|
| 110 |
+
// 7 种颜色从红到紫,重复使用;非 MoE 使用圆形,MoE 使用星形(star)
|
| 111 |
+
const colors = ['#1F77B4','#ff7f0e','#2CA02C','#D62728','#9467BD','#8C564B','#E377C2','#7F7F7F','#BCBD22','#17BECF']
|
| 112 |
+
const lineStyles = ['solid','solid','solid','solid','dash','solid','solid']
|
| 113 |
|
| 114 |
+
// 先构建不带颜色/符号的基础 series(rawSeries),后面会在排序后按顺序分配颜色和符号
|
| 115 |
+
const rawSeries = series.map(s => {
|
| 116 |
+
const baseSize = Number(s.symbolSize) || 8
|
| 117 |
+
return Object.assign({}, s, { symbolSize: baseSize, hoverAnimation: true })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
})
|
| 119 |
|
| 120 |
// 构建 legend.selected 映射:只有 props.autoShowSeries 中的系列会默认显示,其他需手动点击 legend 打开
|
| 121 |
const legendSelected = {}
|
| 122 |
+
if(props.autoShowSeries.includes("*")){
|
| 123 |
+
rawSeries.forEach(s => {
|
| 124 |
+
const name = String(s.name)
|
| 125 |
+
legendSelected[name] = true
|
| 126 |
+
})
|
| 127 |
+
}
|
| 128 |
+
else{
|
| 129 |
+
rawSeries.forEach(s => {
|
| 130 |
const name = String(s.name)
|
| 131 |
legendSelected[name] = Array.isArray(props.autoShowSeries) && props.autoShowSeries.includes(name)
|
| 132 |
})
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
// 按 modelTypeMapping 的定义顺序对 series 排序(使用来自 useLeaderboardData 的 modelTypeGroups)
|
| 137 |
+
const preferredOrder = (modelTypeGroups && Array.isArray(modelTypeGroups.value)) ? modelTypeGroups.value : []
|
| 138 |
+
const orderIndex = {}
|
| 139 |
+
preferredOrder.forEach((n, i) => { orderIndex[String(n)] = i })
|
| 140 |
+
// console.log('Preferred series order:', preferredOrder)
|
| 141 |
+
const inOrder = []
|
| 142 |
+
const rest = []
|
| 143 |
+
rawSeries.forEach(s => {
|
| 144 |
+
const name = String(s.name)
|
| 145 |
+
if (Object.prototype.hasOwnProperty.call(orderIndex, name)) {
|
| 146 |
+
inOrder.push(s)
|
| 147 |
+
} else {
|
| 148 |
+
rest.push(s)
|
| 149 |
+
}
|
| 150 |
+
})
|
| 151 |
+
// 按映射顺序排序 inOrder
|
| 152 |
+
inOrder.sort((a, b) => orderIndex[String(a.name)] - orderIndex[String(b.name)])
|
| 153 |
+
// 其余的按名字字母序 (目前全部指定映射了)
|
| 154 |
+
rest.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
| 155 |
+
|
| 156 |
+
// console.log('Final series order:', inOrder.map(s => s.name), rest.map(s => s.name))
|
| 157 |
+
|
| 158 |
+
const finalSeries = [...inOrder, ...rest]
|
| 159 |
+
|
| 160 |
+
// 现在按 finalSeries 顺序分配颜色和符号,保证颜色顺序与 modelTypeMapping 一致
|
| 161 |
+
const finalSeriesStyled = finalSeries.map((s, idx) => {
|
| 162 |
+
const color = colors[idx % colors.length]
|
| 163 |
+
const lineType = lineStyles[idx % lineStyles.length] || 'solid'
|
| 164 |
+
// 如果 series 名称包含 (MoE) 则使用星形
|
| 165 |
+
const isMoESeries = String(s.name).includes('(MoE)')
|
| 166 |
+
const symbolShape = isMoESeries ? 'star' : 'circle'
|
| 167 |
+
const baseSize = Number(s.symbolSize) || 8
|
| 168 |
+
const emphasisSize = Math.max(12, Math.round(baseSize * 1.6))
|
| 169 |
+
return Object.assign({}, s, {
|
| 170 |
+
lineStyle: { width: 2, type: lineType, color },
|
| 171 |
+
itemStyle: { color },
|
| 172 |
+
symbol: symbolShape,
|
| 173 |
+
symbolSize: baseSize,
|
| 174 |
+
hoverAnimation: true,
|
| 175 |
+
emphasis: {
|
| 176 |
+
focus: 'series',
|
| 177 |
+
symbolSize: emphasisSize,
|
| 178 |
+
itemStyle: { borderWidth: 2, borderColor: '#ffffff' }
|
| 179 |
+
}
|
| 180 |
+
})
|
| 181 |
+
})
|
| 182 |
+
const legendData = finalSeriesStyled.map(s => String(s.name))
|
| 183 |
|
| 184 |
return {
|
| 185 |
color: colors,
|
|
|
|
| 213 |
itemHeight: 10,
|
| 214 |
textStyle: { color: '#cfd8dc' },
|
| 215 |
tooltip: { show: true }
|
| 216 |
+
}, { selected: legendSelected, data: legendData }),
|
| 217 |
grid: { left: '6%', right: '20%', bottom: '8%', containLabel: true },
|
| 218 |
xAxis: {
|
| 219 |
type: 'log',
|
|
|
|
| 244 |
min: yMin,
|
| 245 |
max: yMax
|
| 246 |
},
|
| 247 |
+
series: finalSeriesStyled
|
| 248 |
}
|
| 249 |
}
|
| 250 |
|
|
|
|
| 253 |
function renderChart() {
|
| 254 |
if (!chart) return
|
| 255 |
let rows = leaderboard.value || []
|
| 256 |
+
// 根据 selectedDataNameChart 过滤数据集(如果未选择或为 'all' 则使用全部)
|
| 257 |
+
const sel = selectedDataNameChart && selectedDataNameChart.value ? String(selectedDataNameChart.value) : ''
|
| 258 |
if (sel && sel !== 'all') {
|
| 259 |
rows = rows.filter(r => String(r.data_name ?? '') === sel)
|
| 260 |
}
|
|
|
|
| 279 |
}, { deep: true })
|
| 280 |
|
| 281 |
// 当所选数据集变化时也要重新渲染图表
|
| 282 |
+
watch(selectedDataNameChart, () => {
|
| 283 |
renderChart()
|
| 284 |
})
|
| 285 |
|
|
|
|
| 290 |
</script>
|
| 291 |
|
| 292 |
<template>
|
| 293 |
+
<div ref="chartRef" style="width: 880px; height: 480px;"></div>
|
| 294 |
</template>
|
| 295 |
|
| 296 |
<style scoped></style>
|
src/composables/useLeaderboardData.js
CHANGED
|
@@ -10,6 +10,7 @@ const globalState = reactive({
|
|
| 10 |
visibleColumns: [],
|
| 11 |
dataGroups: [],
|
| 12 |
selectedDataName: '',
|
|
|
|
| 13 |
modelTypeGroups: [],
|
| 14 |
selectedModelType: '',
|
| 15 |
DEFAULT_CSV_PATH: '/finalData/filtered.csv',
|
|
@@ -18,29 +19,35 @@ const globalState = reactive({
|
|
| 18 |
|
| 19 |
// 模型类型映射对象:键为模型类型,值为包含的 model_series 数组
|
| 20 |
const modelTypeMapping = {
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
}
|
| 36 |
|
| 37 |
-
const
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
// 表头显示名称映射(raw header -> 显示名),可以在此添加或由用户修改
|
| 40 |
const headerDisplayMap = reactive({
|
| 41 |
'model_name': 'MODEL NAME',
|
| 42 |
'model_series': 'MODEL SERIES',
|
| 43 |
'model_size (B)': 'MODEL SIZE (B)',
|
|
|
|
| 44 |
'BF16_TFLOPs': 'BF16 TFLOPs',
|
| 45 |
'ic': 'IC',
|
| 46 |
})
|
|
@@ -105,9 +112,26 @@ async function fetchAndLoadCsv(path = globalState.DEFAULT_CSV_PATH) {
|
|
| 105 |
// 选择用于排序/显示的分数字段(优先 information_capacity, ic, 然后尝试 numeric-like fields)
|
| 106 |
const scoreKey = headers.find(h => ['information_capacity', 'ic', 'score'].includes(h)) || headers.find(h => /capacity|score|ic/i.test(h)) || headers[0]
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
// 我们希望确保一些关键列按顺序显示
|
| 112 |
const preferred = ['model_name', 'model_series', 'model_size (B)', 'seq_len', 'uniform_entropy', 'constant', 'conditional_entropy', 'entropy_gain', 'BF16_TFLOPs', 'information_capacity', 'ic']
|
| 113 |
const ordered = []
|
|
@@ -170,14 +194,14 @@ async function fetchAndLoadCsv(path = globalState.DEFAULT_CSV_PATH) {
|
|
| 170 |
}
|
| 171 |
|
| 172 |
// 格式化 helper
|
| 173 |
-
const numericFloatCols = new Set(['uniform_entropy','conditional_entropy','entropy_gain','information_capacity','ic','constant','BF16_TFLOPs'])
|
| 174 |
const numericIntCols = new Set(['seq_len'])
|
| 175 |
// attach formatter per row for rendering convenience (non-reactive simple values)
|
| 176 |
for (const row of globalState.leaderboard) {
|
| 177 |
row._formatted = {}
|
| 178 |
for (const h of ordered) {
|
| 179 |
const raw = row[h]
|
| 180 |
-
if (raw == null || raw === '') { row._formatted[h] = ''
|
| 181 |
if (numericIntCols.has(h)) {
|
| 182 |
const n = Number(raw)
|
| 183 |
row._formatted[h] = Number.isFinite(n) ? String(Math.round(n)) : raw
|
|
@@ -237,6 +261,10 @@ export function useLeaderboardData() {
|
|
| 237 |
get: () => globalState.selectedDataName,
|
| 238 |
set: (v) => globalState.selectedDataName = v
|
| 239 |
}),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
modelTypeGroups: computed(() => globalState.modelTypeGroups),
|
| 241 |
selectedModelType: computed({
|
| 242 |
get: () => globalState.selectedModelType,
|
|
|
|
| 10 |
visibleColumns: [],
|
| 11 |
dataGroups: [],
|
| 12 |
selectedDataName: '',
|
| 13 |
+
selectedDataNameChart: '',
|
| 14 |
modelTypeGroups: [],
|
| 15 |
selectedModelType: '',
|
| 16 |
DEFAULT_CSV_PATH: '/finalData/filtered.csv',
|
|
|
|
| 19 |
|
| 20 |
// 模型类型映射对象:键为模型类型,值为包含的 model_series 数组
|
| 21 |
const modelTypeMapping = {
|
| 22 |
+
'Qwen3': ['Qwen3'],
|
| 23 |
+
'Qwen2.5': ['Qwen2.5'],
|
| 24 |
+
'Qwen2': ['Qwen2'],
|
| 25 |
+
'Qwen1.5': ['Qwen1.5'],
|
| 26 |
+
'Llama-3': ['Llama-3.1', 'Llama-3.2'],
|
| 27 |
+
'InternLM2.5': ['internlm2.5'],
|
| 28 |
+
'GLM-4': ['glm-4', 'GLM-4'],
|
| 29 |
+
'Seed-OSS': ['Seed-OSS'],
|
| 30 |
+
'Gemma-3': ['gemma-3'],
|
| 31 |
+
'Hunyuan': ['Hunyuan'],
|
| 32 |
+
'Qwen2 (MoE)': ['Qwen2 (MoE)'],
|
| 33 |
+
'Qwen1.5 (MoE)': ['Qwen1.5 (MoE)'],
|
| 34 |
+
'DeepSeek-V3.1': ['DeepSeek-V3.1-Base'],
|
| 35 |
+
'DeepSeek-V2': ['DeepSeek-V2'],
|
| 36 |
+
'GLM-4.5': ['GLM-4.5-Air-Base', 'GLM-4.5-Base'],
|
| 37 |
+
'Llama-4': ['Llama-4'],
|
| 38 |
}
|
| 39 |
|
| 40 |
+
const MoEModelSeries = ['Qwen2', 'Qwen1.5']
|
| 41 |
+
|
| 42 |
+
// const autoShowSeries = ['Qwen3', 'Llama-3', 'InternLM2.5', 'GLM-4', 'Seed-OSS', 'Gemma-3', 'Hunyuan', 'DeepSeek-V3.1', 'DeepSeek-V2', 'GLM-4.5']
|
| 43 |
+
const autoShowSeries = ["*"]
|
| 44 |
|
| 45 |
// 表头显示名称映射(raw header -> 显示名),可以在此添加或由用户修改
|
| 46 |
const headerDisplayMap = reactive({
|
| 47 |
'model_name': 'MODEL NAME',
|
| 48 |
'model_series': 'MODEL SERIES',
|
| 49 |
'model_size (B)': 'MODEL SIZE (B)',
|
| 50 |
+
'conditional_entropy': 'Conditional Entropy',
|
| 51 |
'BF16_TFLOPs': 'BF16 TFLOPs',
|
| 52 |
'ic': 'IC',
|
| 53 |
})
|
|
|
|
| 112 |
// 选择用于排序/显示的分数字段(优先 information_capacity, ic, 然后尝试 numeric-like fields)
|
| 113 |
const scoreKey = headers.find(h => ['information_capacity', 'ic', 'score'].includes(h)) || headers.find(h => /capacity|score|ic/i.test(h)) || headers[0]
|
| 114 |
|
| 115 |
+
// 按 scoreKey 排序(保持所有字段),然后把每行写入 leaderboard,同时记录 headers
|
| 116 |
+
rows.sort((a, b) => (Number(b[scoreKey]) || 0) - (Number(a[scoreKey]) || 0))
|
| 117 |
|
| 118 |
+
// 特定处理
|
| 119 |
+
// 判断模型开头是否在 MoEModelSeries 中,是则在 判断尾部是否为-A{number}B这样的格式
|
| 120 |
+
for (const r of rows) {
|
| 121 |
+
const name = r['model_name'] || ''
|
| 122 |
+
for (const moePrefix of MoEModelSeries) {
|
| 123 |
+
if (name.startsWith(moePrefix)) {
|
| 124 |
+
// console.log('Checking MoE model name:', name,name.match(/-A(\d+(?:\.\d+)?)B/))
|
| 125 |
+
const moeSuffixMatch = name.match(/-A(.+)B$/)
|
| 126 |
+
if (moeSuffixMatch) {
|
| 127 |
+
// 更改 model_series 显示名称 为 moePrefix + ' (MoE)'
|
| 128 |
+
r['model_series'] = `${moePrefix} (MoE)`
|
| 129 |
+
console.log('Detected MoE model, updated series:', r['model_series'])
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
// console.log('rows', rows)
|
| 135 |
// 我们希望确保一些关键列按顺序显示
|
| 136 |
const preferred = ['model_name', 'model_series', 'model_size (B)', 'seq_len', 'uniform_entropy', 'constant', 'conditional_entropy', 'entropy_gain', 'BF16_TFLOPs', 'information_capacity', 'ic']
|
| 137 |
const ordered = []
|
|
|
|
| 194 |
}
|
| 195 |
|
| 196 |
// 格式化 helper
|
| 197 |
+
const numericFloatCols = new Set(['uniform_entropy', 'conditional_entropy', 'entropy_gain', 'information_capacity', 'ic', 'constant', 'BF16_TFLOPs'])
|
| 198 |
const numericIntCols = new Set(['seq_len'])
|
| 199 |
// attach formatter per row for rendering convenience (non-reactive simple values)
|
| 200 |
for (const row of globalState.leaderboard) {
|
| 201 |
row._formatted = {}
|
| 202 |
for (const h of ordered) {
|
| 203 |
const raw = row[h]
|
| 204 |
+
if (raw == null || raw === '') { row._formatted[h] = ''; continue }
|
| 205 |
if (numericIntCols.has(h)) {
|
| 206 |
const n = Number(raw)
|
| 207 |
row._formatted[h] = Number.isFinite(n) ? String(Math.round(n)) : raw
|
|
|
|
| 261 |
get: () => globalState.selectedDataName,
|
| 262 |
set: (v) => globalState.selectedDataName = v
|
| 263 |
}),
|
| 264 |
+
selectedDataNameChart: computed({
|
| 265 |
+
get: () => globalState.selectedDataNameChart,
|
| 266 |
+
set: (v) => globalState.selectedDataNameChart = v
|
| 267 |
+
}),
|
| 268 |
modelTypeGroups: computed(() => globalState.modelTypeGroups),
|
| 269 |
selectedModelType: computed({
|
| 270 |
get: () => globalState.selectedModelType,
|
src/views/Leaderboard.vue
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
| 13 |
selectableColumns,
|
| 14 |
dataGroups,
|
| 15 |
selectedDataName,
|
|
|
|
| 16 |
autoShowSeries,
|
| 17 |
headerDisplayMap,
|
| 18 |
dataNameDisplayMap,
|
|
@@ -67,23 +68,24 @@ function toggleTheme() {
|
|
| 67 |
</script>
|
| 68 |
|
| 69 |
<template>
|
| 70 |
-
<div
|
|
|
|
| 71 |
<div class=" mx-auto">
|
| 72 |
<header class="mb-6 flex items-start justify-between">
|
| 73 |
<div>
|
| 74 |
-
<h1 class="text-3xl font-extrabold text-gray-900 dark:text-gray-100">LLM
|
| 75 |
-
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300"
|
| 76 |
</div>
|
| 77 |
</header>
|
| 78 |
|
| 79 |
-
|
| 80 |
|
| 81 |
<div class="bg-white dark:bg-gray-900/60 shadow rounded-lg overflow-hidden">
|
| 82 |
<div
|
| 83 |
class="px-6 py-4 border-b bg-gradient-to-r from-indigo-50 via-white to-white dark:bg-gradient-to-r dark:from-gray-800 dark:via-gray-900 dark:to-gray-900">
|
| 84 |
<div class="flex items-center justify-between">
|
| 85 |
<h2 class="text-lg font-medium text-gray-800 dark:text-gray-50">排行榜</h2>
|
| 86 |
-
<div class="text-sm text-gray-500 dark:text-gray-300"
|
| 87 |
</div>
|
| 88 |
</div>
|
| 89 |
|
|
@@ -95,8 +97,11 @@ function toggleTheme() {
|
|
| 95 |
<!-- <label class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer" :class="{'bg-gray-100 dark:bg-gray-700': selectedDataName==='all'}">
|
| 96 |
<input type="radio" class="hidden" value="all" v-model="selectedDataName" /> 全部
|
| 97 |
</label> -->
|
| 98 |
-
<label v-for="g in dataGroups" :key="g"
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
| 100 |
</label>
|
| 101 |
</div>
|
| 102 |
<div v-else class="text-sm text-gray-500 dark:text-gray-400">(数据集中未检测到 data_name 列)</div>
|
|
@@ -108,10 +113,14 @@ function toggleTheme() {
|
|
| 108 |
<div class="flex items-center gap-3">
|
| 109 |
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">模型类型筛选</span>
|
| 110 |
<div v-if="modelTypeGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
|
| 111 |
-
<label
|
|
|
|
|
|
|
| 112 |
<input type="radio" class="hidden" value="all" v-model="selectedModelType" /> 全部
|
| 113 |
</label>
|
| 114 |
-
<label v-for="g in modelTypeGroups" :key="g"
|
|
|
|
|
|
|
| 115 |
<input type="radio" class="hidden" :value="g" v-model="selectedModelType" /> {{ g }}
|
| 116 |
</label>
|
| 117 |
</div>
|
|
@@ -125,15 +134,19 @@ function toggleTheme() {
|
|
| 125 |
<div class="flex items-center gap-3">
|
| 126 |
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">显示列</span>
|
| 127 |
<div class="flex items-center gap-2">
|
| 128 |
-
<button @click.prevent="selectAll"
|
| 129 |
-
|
|
|
|
|
|
|
| 130 |
</div>
|
| 131 |
</div>
|
| 132 |
|
| 133 |
<div class="flex-1 overflow-x-auto">
|
| 134 |
<div class="flex gap-2 items-center px-1 py-2">
|
| 135 |
-
<label v-for="h in selectableColumns" :key="h"
|
| 136 |
-
|
|
|
|
|
|
|
| 137 |
<span class="truncate text-gray-800 dark:text-gray-50">{{ headerDisplayMap[h] || h }}</span>
|
| 138 |
</label>
|
| 139 |
</div>
|
|
@@ -144,29 +157,51 @@ function toggleTheme() {
|
|
| 144 |
<div class="p-4 overflow-x-auto">
|
| 145 |
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
| 146 |
<thead class="bg-gray-50 dark:bg-gradient-to-r dark:from-gray-800 dark:to-gray-900">
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
| 153 |
</thead>
|
| 154 |
<tbody class="bg-white dark:bg-transparent divide-y divide-gray-100 dark:divide-gray-700">
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
</tbody>
|
| 167 |
</table>
|
| 168 |
</div>
|
| 169 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
</div>
|
| 171 |
</div>
|
| 172 |
</template>
|
|
|
|
| 13 |
selectableColumns,
|
| 14 |
dataGroups,
|
| 15 |
selectedDataName,
|
| 16 |
+
selectedDataNameChart,
|
| 17 |
autoShowSeries,
|
| 18 |
headerDisplayMap,
|
| 19 |
dataNameDisplayMap,
|
|
|
|
| 68 |
</script>
|
| 69 |
|
| 70 |
<template>
|
| 71 |
+
<div
|
| 72 |
+
class="min-h-screen bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:via-gray-800 dark:to-gray-800 py-12 px-4 sm:px-6 lg:px-8">
|
| 73 |
<div class=" mx-auto">
|
| 74 |
<header class="mb-6 flex items-start justify-between">
|
| 75 |
<div>
|
| 76 |
+
<h1 class="text-3xl font-extrabold text-gray-900 dark:text-gray-100">LLM Information Capacity</h1>
|
| 77 |
+
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">LLM IC Leaderboard Demo</p>
|
| 78 |
</div>
|
| 79 |
</header>
|
| 80 |
|
| 81 |
+
|
| 82 |
|
| 83 |
<div class="bg-white dark:bg-gray-900/60 shadow rounded-lg overflow-hidden">
|
| 84 |
<div
|
| 85 |
class="px-6 py-4 border-b bg-gradient-to-r from-indigo-50 via-white to-white dark:bg-gradient-to-r dark:from-gray-800 dark:via-gray-900 dark:to-gray-900">
|
| 86 |
<div class="flex items-center justify-between">
|
| 87 |
<h2 class="text-lg font-medium text-gray-800 dark:text-gray-50">排行榜</h2>
|
| 88 |
+
<div class="text-sm text-gray-500 dark:text-gray-300">更新于xx年xx月</div>
|
| 89 |
</div>
|
| 90 |
</div>
|
| 91 |
|
|
|
|
| 97 |
<!-- <label class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer" :class="{'bg-gray-100 dark:bg-gray-700': selectedDataName==='all'}">
|
| 98 |
<input type="radio" class="hidden" value="all" v-model="selectedDataName" /> 全部
|
| 99 |
</label> -->
|
| 100 |
+
<label v-for="g in dataGroups" :key="g"
|
| 101 |
+
class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer dark:text-slate-300"
|
| 102 |
+
:class="{ 'bg-gray-100 dark:bg-gray-700 dark:text-white': selectedDataName === g }">
|
| 103 |
+
<input type="radio" class="hidden" :value="g" v-model="selectedDataName" /> {{ dataNameDisplayMap[g] ?
|
| 104 |
+
dataNameDisplayMap[g] : g }}
|
| 105 |
</label>
|
| 106 |
</div>
|
| 107 |
<div v-else class="text-sm text-gray-500 dark:text-gray-400">(数据集中未检测到 data_name 列)</div>
|
|
|
|
| 113 |
<div class="flex items-center gap-3">
|
| 114 |
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">模型类型筛选</span>
|
| 115 |
<div v-if="modelTypeGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
|
| 116 |
+
<label
|
| 117 |
+
class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer dark:text-slate-300"
|
| 118 |
+
:class="{ 'bg-gray-100 dark:bg-gray-700 dark:text-white': selectedModelType === 'all' }">
|
| 119 |
<input type="radio" class="hidden" value="all" v-model="selectedModelType" /> 全部
|
| 120 |
</label>
|
| 121 |
+
<label v-for="g in modelTypeGroups" :key="g"
|
| 122 |
+
class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer dark:text-slate-300"
|
| 123 |
+
:class="{ 'bg-gray-100 dark:bg-gray-700 dark:text-white': selectedModelType === g }">
|
| 124 |
<input type="radio" class="hidden" :value="g" v-model="selectedModelType" /> {{ g }}
|
| 125 |
</label>
|
| 126 |
</div>
|
|
|
|
| 134 |
<div class="flex items-center gap-3">
|
| 135 |
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">显示列</span>
|
| 136 |
<div class="flex items-center gap-2">
|
| 137 |
+
<button @click.prevent="selectAll"
|
| 138 |
+
class="text-sm px-2 py-1 bg-white/90 dark:bg-gray-700/60 border border-gray-200 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-white dark:hover:bg-gray-600 transition">全选</button>
|
| 139 |
+
<button @click.prevent="clearAll"
|
| 140 |
+
class="text-sm px-2 py-1 bg-transparent border border-gray-200 dark:border-gray-700 rounded text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition">清除</button>
|
| 141 |
</div>
|
| 142 |
</div>
|
| 143 |
|
| 144 |
<div class="flex-1 overflow-x-auto">
|
| 145 |
<div class="flex gap-2 items-center px-1 py-2">
|
| 146 |
+
<label v-for="h in selectableColumns" :key="h"
|
| 147 |
+
class="flex items-center gap-2 text-sm bg-white dark:bg-gray-800 px-3 py-1 rounded-full border border-gray-200 dark:border-gray-700 hover:shadow-sm whitespace-nowrap">
|
| 148 |
+
<input type="checkbox" :value="h" v-model="visibleColumns"
|
| 149 |
+
class="h-4 w-4 text-indigo-600 dark:text-indigo-400 bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 rounded" />
|
| 150 |
<span class="truncate text-gray-800 dark:text-gray-50">{{ headerDisplayMap[h] || h }}</span>
|
| 151 |
</label>
|
| 152 |
</div>
|
|
|
|
| 157 |
<div class="p-4 overflow-x-auto">
|
| 158 |
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
| 159 |
<thead class="bg-gray-50 dark:bg-gradient-to-r dark:from-gray-800 dark:to-gray-900">
|
| 160 |
+
<tr>
|
| 161 |
+
<th
|
| 162 |
+
class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
| 163 |
+
#</th>
|
| 164 |
+
<th v-for="h in displayedColumns" :key="h"
|
| 165 |
+
class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
| 166 |
+
{{ headerDisplayMap[h] || h }}
|
| 167 |
+
</th>
|
| 168 |
+
</tr>
|
| 169 |
</thead>
|
| 170 |
<tbody class="bg-white dark:bg-transparent divide-y divide-gray-100 dark:divide-gray-700">
|
| 171 |
+
<tr v-for="model in filteredLeaderboard" :key="model.rank" :class="{
|
| 172 |
+
'bg-yellow-50 dark:bg-yellow-700/25': model.rank === 1,
|
| 173 |
+
'bg-gray-50 dark:bg-gray-800/60': model.rank === 2,
|
| 174 |
+
'bg-indigo-50 dark:bg-indigo-700/25': model.rank === 3
|
| 175 |
+
}" class="hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-150">
|
| 176 |
+
<td class="px-4 py-3 whitespace-nowrap font-medium text-gray-800 dark:text-gray-50">#{{ model.rank }}
|
| 177 |
+
</td>
|
| 178 |
+
<td v-for="h in displayedColumns" :key="h" class="px-4 py-3 whitespace-nowrap">
|
| 179 |
+
<div class="text-sm text-gray-800 dark:text-gray-50">{{ formatCell(h, model) }}</div>
|
| 180 |
+
</td>
|
| 181 |
+
</tr>
|
| 182 |
</tbody>
|
| 183 |
</table>
|
| 184 |
</div>
|
| 185 |
</div>
|
| 186 |
+
|
| 187 |
+
<Chart :autoShowSeries="autoShowSeries" />
|
| 188 |
+
<div class="px-6 py-3 border-b bg-gray-50 dark:bg-gradient-to-b dark:from-gray-900 dark:to-gray-800">
|
| 189 |
+
<div class="flex items-center gap-3">
|
| 190 |
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">数据集筛选</span>
|
| 191 |
+
<div v-if="dataGroups.length > 0" class="flex gap-2 items-center overflow-x-auto">
|
| 192 |
+
<!-- <label class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer" :class="{'bg-gray-100 dark:bg-gray-700': selectedDataName==='all'}">
|
| 193 |
+
<input type="radio" class="hidden" value="all" v-model="selectedDataName" /> 全部
|
| 194 |
+
</label> -->
|
| 195 |
+
<label v-for="g in dataGroups" :key="g"
|
| 196 |
+
class="px-2 py-1 rounded-full border border-gray-200 dark:border-gray-700 text-sm cursor-pointer dark:text-slate-300"
|
| 197 |
+
:class="{ 'bg-gray-100 dark:bg-gray-700 dark:text-white': selectedDataNameChart === g }">
|
| 198 |
+
<input type="radio" class="hidden" :value="g" v-model="selectedDataNameChart" /> {{ dataNameDisplayMap[g]
|
| 199 |
+
? dataNameDisplayMap[g] : g }}
|
| 200 |
+
</label>
|
| 201 |
+
</div>
|
| 202 |
+
<div v-else class="text-sm text-gray-500 dark:text-gray-400">(数据集中未检测到 data_name 列)</div>
|
| 203 |
+
</div>
|
| 204 |
+
</div>
|
| 205 |
</div>
|
| 206 |
</div>
|
| 207 |
</template>
|