Spaces:
Running
Running
File size: 4,635 Bytes
bb59211 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | /** 「训练参数」页签 —— 渲染 train_config.json 里的真实超参。
*
* 数据是**训练当时实际用的那份配置**(lerobot-train 自己写进 checkpoint 的),
* 不是我们事后补的默认值。
*
* 缺字段就**整行不渲染**。不同 LeRobot 版本的键名会变(我们比参考实现少
* `scheduler_state.json`、多四个 pre/postprocessor 文件就是同一类差异),写死
* "缺了就显示 N/A"会让人误以为训练时那个参数是 null。
*/
import { el, clear } from '../shared/dom.js';
import { thousands } from '../shared/format.js';
import { tr } from '../shared/text.js';
/** 分组 → 每行 [显示名, 取值路径, 可选格式化函数]。
*
* 分组标题存的是 **i18n key** 而不是翻好的文字:它是模块级常量,在这里调
* `tr()` 会在模块加载那一刻定死,用户之后再切语言就不会跟着变。渲染时再取。 */
const GROUPS = [
{
titleKey: 'train_group_train',
titleFallback: 'Training',
rows: [
['steps', 'steps', thousands],
['batch_size', 'batch_size'],
['save_freq', 'save_freq', thousands],
['seed', 'seed'],
['num_workers', 'num_workers'],
],
},
{
titleKey: 'train_group_policy',
titleFallback: 'Policy',
rows: [
['type', 'policy.type'],
['dim_model', 'policy.dim_model'],
['chunk_size', 'policy.chunk_size'],
['n_action_steps', 'policy.n_action_steps'],
['vision_backbone', 'policy.vision_backbone'],
['kl_weight', 'policy.kl_weight'],
],
},
{
titleKey: 'train_group_optimizer',
titleFallback: 'Optimizer',
rows: [
['type', 'optimizer.type'],
['lr', 'optimizer.lr'],
['weight_decay', 'optimizer.weight_decay'],
['grad_clip_norm', 'optimizer.grad_clip_norm'],
],
},
{
titleKey: 'train_group_dataset',
titleFallback: 'Dataset',
rows: [
['repo_id', 'dataset.repo_id'],
['root', 'dataset.root'],
['eval_split', 'dataset.eval_split'],
['episodes', 'dataset.episodes'],
],
},
];
/** 按点号路径取值;任一层缺失返回 undefined(不抛)。 */
function pick(obj, path) {
return path.split('.').reduce(
(acc, key) => (acc === null || acc === undefined ? undefined : acc[key]),
obj,
);
}
/** 值 → 显示文本。null/undefined/空串统一成 '—',数组/对象转紧凑 JSON。 */
function display(value, format) {
if (value === null || value === undefined || value === '') return '—';
if (format) return format(value);
if (Array.isArray(value) || typeof value === 'object') {
return JSON.stringify(value);
}
return String(value);
}
function renderGroup(group, config) {
// 先算一遍哪些行有值 —— 全组都没值的分组直接不渲染,避免出现空卡片。
const present = group.rows
.map(([label, path, format]) => [label, pick(config, path), format])
.filter(([, value]) => value !== undefined && value !== null);
if (!present.length) return null;
return el('div', { class: 'bg-gray-900 rounded-lg border border-gray-800 p-4' }, [
el('div', {
class: 'text-xs font-semibold text-gray-400 mb-3',
text: tr(group.titleKey, group.titleFallback),
}),
el('div', { class: 'space-y-1.5' }, present.map(([label, value, format]) => (
el('div', { class: 'flex items-baseline gap-3 text-xs' }, [
el('span', { class: 'text-gray-500 shrink-0 w-32', text: label }),
el('span', {
class: 'text-gray-200 font-mono break-all',
text: display(value, format),
}),
])
))),
]);
}
/**
* @param {HTMLElement} container
* @param {object} config train_config.json 的内容
* @param {{step: number}} meta 这份配置取自哪个 checkpoint
*/
export function renderParams(container, config, meta = {}) {
clear(container);
if (!config || Object.keys(config).length === 0) {
container.appendChild(emptyState(
tr('train_params_empty',
'No train_config.json in this checkpoint; parameters unavailable.'),
));
return;
}
container.appendChild(el('div', {
class: 'text-xs text-gray-500 mb-4',
text: `${tr('train_params_source', 'Source:')} `
+ `checkpoints/${meta.step ?? '?'}/pretrained_model/train_config.json`,
}));
const cards = GROUPS.map((g) => renderGroup(g, config)).filter(Boolean);
container.appendChild(
el('div', { class: 'grid grid-cols-1 md:grid-cols-2 gap-4' }, cards),
);
}
function emptyState(message) {
return el('div', {
class: 'text-center text-gray-600 text-sm py-10',
text: message,
});
}
|