__tests__/merge-one-model.test.js
Rama main · réplica de lectura del repositorio original.
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
const fs = require('fs');
const os = require('os');
const path = require('path');
/*
* Averaging two adapters adds their matrices, which means something only when both were fitted to
* the same network. Two students training side by side on two cards are not shares of one batch
* and must never be treated as any: across models it is a shape error at load, after the hours
* are spent and with both shares consumed.
*/
describe('a batch merges one model only', () => {
let dir, training;
const write = (rows) => {
fs.mkdirSync(path.join(dir, 'training'), { recursive: true });
fs.writeFileSync(path.join(dir, 'training', 'rounds.json'), JSON.stringify(rows));
};
const share = (id, batch, model, extra = {}) => ({
id, batch, share: 2, merge: false, status: 'done', discarded: false,
device: id, scope: { level: 'base', name: '', key: 'base' },
recipe: { base: model }, adapterHub: `hub:${id}`,
result: { agreement_pct: 30, collapse: { ratio: 1.2, tool: 'read' } },
baseline: { agreement_pct: 20 },
...extra,
});
beforeEach(() => {
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gb-merge-'));
process.env.PROFILE_DIR = dir;
delete require.cache[require.resolve('../src/training')];
training = require('../src/training');
});
afterEach(() => {
delete process.env.PROFILE_DIR;
delete require.cache[require.resolve('../src/training')];
fs.rmSync(dir, { recursive: true, force: true });
});
it('two shares of one model are ready to merge', () => {
write([
share('r-1', 'b-1', 'Qwen/Qwen2.5-0.5B-Instruct'),
share('r-2', 'b-1', 'Qwen/Qwen2.5-0.5B-Instruct'),
]);
const out = training.batchReadyToMerge('r-1');
expect(out).toBeTruthy();
expect(out.abandoned).toBeFalsy();
});
it('two models in one batch are abandoned, with the reason', () => {
write([
share('r-1', 'b-1', 'Qwen/Qwen2.5-0.5B-Instruct'),
share('r-2', 'b-1', 'Qwen/Qwen3-0.6B'),
]);
const out = training.batchReadyToMerge('r-1');
expect(out.abandoned).toBe(true);
expect(out.why).toMatch(/cannot be averaged/);
expect(out.why).toContain('Qwen/Qwen3-0.6B');
});
it('two rounds with no batch at all are simply two rounds', () => {
write([
{ ...share('r-1', '', 'Qwen/Qwen2.5-0.5B-Instruct'), share: 1 },
{ ...share('r-2', '', 'Qwen/Qwen3-0.6B'), share: 1 },
]);
/* No batch id: nothing to merge, and nothing claimed. */
expect(training.batchReadyToMerge('r-1')).toBe(null);
expect(training.batchReadyToMerge('r-2')).toBe(null);
});
});