O que Programação por Espaço de Pesos faz?
Melhore um modelo pequeno e aberto em um objetivo editando seus pesos diretamente, sem otimizador. Isso segue o Experimento 8 de "Sparks of Weight-Space Programming" (Takehi, Teoh, Zhang, Khattab, MIT CSAIL, 2 de Novembro de 2026): os loops de pensamento do Qwen3.5-0.8B foram atribuídos a uma direção residual escrita pelos MLPs das camadas 15 e 19, e uma edição de posto 1 em 64 colunas down_proj por camada elevou o MATH-500 de 7,9% para 48,1%.
Siga o caminho rápido abaixo em ordem. Cada script está agrupado em scripts/ e testado. Não redesenhe o pipeline ou escreva novas ferramentas a menos que uma etapa falhe.
O que a skill garante, e o que não pode. Nenhum método pode prometer que uma edição ajude: alguns modelos não têm um único hábito para desligar. A skill garante o processo:
- sem perda de trabalho: falhas reiniciam com um lote menor e retomam;
- nenhuma edição chega ao teste separado sem passar por uma verificação prática medida;
- uma falha na verificação escala através de etapas fixas em vez de parar;
- cada execução termina com um veredito claro e números, e a cópia é restaurada se nada funcionar.
Referências, apenas para detalhes:
references/goals.md: outros objetivos; como escrever pares correspondentes; avaliadores; receitas.references/engines.md: qual engine em qual SO e GPU, instalação, dimensionamento, armadilhas, teste de fumaça.references/evaluation.md: divisões, protocolo de amostragem, especificidades do MMLU-Pro, estatísticas pareadas, modelo de relatório.references/method.md: a matemática de cada etapa e a receita exata dos autores.
Caminho rápido
Defina quatro placeholders uma vez (PowerShell: $env:SKILL = "...", depois $env:SKILL):
SKILL=<pasta desta skill> BASE=<pasta do modelo base> COPY=<pasta do modelo base>-<objetivo> WORK=<pasta de resultados>
Execute cada trabalho de geração em segundo plano (Claude Code: a ferramenta Bash run_in_background, não um & do shell) e aguarde seu aviso de conclusão em vez de sondar. A geração domina o tempo, então informe o usuário sobre o orçamento antecipadamente: 1,5–3 horas em uma GPU de consumo para os presets, muito menos com vLLM em uma GPU de datacenter.
0. Pergunte apenas o que precisa (uma pergunta). Pergunte pelo objetivo. Se for matemática ou ciência, use o preset: sem mais perguntas. Caso contrário, também pergunte ou ajude a escrever:
- prompts que mostram a falha, como linhas JSON
{"id", "prompt", "answer"?, "group"?}; - como pontuá-los (
references/goals.mdseção 3); - para comportamentos que não são loops, 12+ pares correspondentes (seção 2).
Confirme "editar pesos, sem treinamento".
1. Verifique a máquina (2 min): python $SKILL/scripts/device.py --model $BASE --jobs 2. Em uma máquina nova, execute também o teste de fumaça em references/engines.md seção 6.
2. Copie o modelo (1 min). Cada edição vai para os arquivos desta cópia; cada avaliação carrega a cópia.
python $SKILL/scripts/copy_model.py --base $BASE --copy $COPY
3. Divisões (segundos).
- Preset de Ciência:
python $SKILL/scripts/make_splits.py --task mmlu --source <parquet de teste MMLU-Pro> --out $WORK/splits. - Preset de Matemática: o mesmo com
--task math --source <teste MATH-500.jsonl> --practice 25 --heldout 75. - Objetivo próprio: divida seu arquivo de prompt em
practice.jsonleheldout.jsonlpor uma regra fixa, mais umtest_ids.txtcom cerca de 30 ids separados, todos escolhidos antes de qualquer resposta ser vista.
Abaixo, T é a tarefa (mmlu, math ou jsonl) e G são as opções do avaliador (nenhum para os presets; veja references/goals.md caso contrário).
4. Inicie duas execuções base ao mesmo tempo:
python $SKILL/scripts/run_eval.py --task $T --model $BASE --label base --data $WORK/splits/practice.jsonl --out $WORK/practice/base.jsonl --max-tokens 16384 --jobs 2
python $SKILL/scripts/run_eval.py --task $T --model $BASE --label base --data $WORK/splits/heldout.jsonl --ids-file $WORK/splits/test_ids.txt --out $WORK/test/base.jsonl --jobs 2
Use a amostragem do cartão do modelo para outros modelos (--profile, ou os sinalizadores de substituição; references/engines.md). A execução separada não depende da edição.
5. Obtenha o contraste.
- Loops (presets, ou qualquer objetivo onde as respostas atingem o limite): loops aparecem apenas quando uma resposta atinge o limite. Conte-os com
grep -c '"length"' $WORK/practice/base.jsonl; com 15 ou mais, pare a execução de prática. - Outros comportamentos: você precisa dos pares, e a execução de prática só precisa terminar para fornecer a linha de base.
Em seguida, avalie:
python $SKILL/scripts/grade.py --task $T $G $WORK/practice/base.jsonl --out $WORK/practice/summary.json
Prossiga ou pare. Estime a margem para ganho: a parcela de respostas mostrando o comportamento, vezes a pontuação nas respostas que não o mostram. Se for inferior a cerca de 10%, avise o usuário para esperar pouco antes de prosseguir.
6. Leia a direção, construa candidatos, filtre (minutos, apenas passes para frente). Loops:
python $SKILL/scripts/probe.py collect --task $T --model $BASE --data $WORK/splits/practice.jsonl --runs $WORK/practice/base.jsonl --out $WORK/probes
Pares (adicione --runs, --data e --task para janelas de efeito colateral limpas):
python $SKILL/scripts/probe.py collect-pairs --model $BASE --pairs pairs.jsonl --thinking on --out $WORK/probes
Em seguida, construa os candidatos da primeira rodada, o controle e filtre:
for spec in "15 19:128:6" "15 19:256:6" "15 19:64:6" "11 15 17 19 21:128:6"; do IFS=: read L K A <<< "$spec"; python $SKILL/scripts/probe.py build --probes $WORK/probes --model $BASE --layers $L --k $K --alpha $A --out "$WORK/edits/L${L// /-}_k${K}_a${A}.safetensors"; done
python $SKILL/scripts/probe.py build --probes $WORK/probes --model $BASE --layers 15 19 --k 128 --alpha 6 --control random --out $WORK/edits/random.safetensors
python $SKILL/scripts/probe.py screen --probes $WORK/probes --model $BASE --edits $WORK/edits/*.safetensors
No Windows PowerShell, execute cada linha build separadamente. As camadas acima servem para um modelo de 24 camadas. Para outros, use as camadas em cerca de 60% e 80% da profundidade, uma propagação de cinco camadas e a mais profunda sozinha.
Classifique os candidatos pela queda no unwanted, mantendo apenas aqueles cujas quedas em wanted e clean são menores que um terço dela. O controle aleatório deve permanecer próximo de 0; se ele se mover tanto quanto o melhor candidato, a direção não é específica, então vá para a etapa de parada.
7. Escreva o melhor candidato na cópia e verifique (1 min). Nunca avalie uma edição na memória.
python $SKILL/scripts/apply_edit.py --base $BASE --copy $COPY --edit $WORK/edits/<candidato>.safetensors
python $SKILL/scripts/diff_edit.py --base $BASE --edited $COPY --out $WORK/edit_report.json
A diferença deve mostrar apenas os tensores down_proj pretendidos, changed_columns = k, uma parcela de posto 1 de cerca de 1 e uma mediana α de cerca do α escolhido.
8. Verificação prática: o portão (15–25 min, junto com a execução base separada). Execute a cópia em cada pergunta prática que a base falhou (corte, errada ou sinalizada), mais 6 que ela lidou bem:
python -c "import json; g=json.load(open('$WORK/practice/summary.json'))['graded']['base']; bad=[x['id'] for x in g if x['cut'] or x['correct'] is False or x['flagged']]; ok=[x['id'] for x in g if x['id'] not in bad]; open('$WORK/check_ids.txt','w').write('\n'.join(bad+ok[::max(1,len(ok)//6)][:6])+'\n')"
python $SKILL/scripts/run_eval.py --task $T --model $COPY --label copy --data $WORK/splits/practice.jsonl --ids-file $WORK/check_ids.txt --out $WORK/practice/copy.jsonl --max-tokens 16384 --jobs 2
python $SKILL/scripts/grade.py --task $T $G $WORK/practice/base.jsonl $WORK/practice/copy.jsonl
O portão passa apenas se todos os três se mantiverem:
- corrigido ≥ max(3, 20% das falhas da base verificadas),
- quebrado ≤ corrigido ÷ 4,
- e, para objetivos de loop, a parcela de corte cai em pelo menos um quarto.
Passe: vá para a etapa 10. Falhe: etapa 9.
9. Escale nesta ordem; cada degrau é mais uma verificação prática.
- O próximo candidato na classificação. Restaure a cópia (
copy_model.py --base $BASE --copy $COPY --restore), aplique-o e repita as etapas 7–8. Faça isso para no máximo mais dois candidatos. - Rodada 2, quando o comportamento se moveu em vez de desaparecer. Por exemplo, o pensamento agora fecha, mas o loop recomeça após
response, ou um novo padrão indesejado aparece nas respostas da cópia. Trate a cópia editada como a nova base:- faça
COPY2comcopy_model.py --base $COPY --copy $COPY2; - execute
probe.py collect(oucollect-pairs) com--model $COPY --runs $WORK/practice/copy.jsonl; - construa e filtre com
--model $COPY; - aplique com
--base $COPY --copy $COPY2; - verifique
COPY2como na etapa 8.
- faça
- Pare. Nenhum ganho confiável após três verificações (ou após o orçamento de tempo definido pelo usuário). Restaure a cópia e relate o veredito "nenhum ganho confiável" com os números de cada candidato. Para o Qwen3.5-0.8B, ofereça a edição publicada de matemática como comparação (ela reduziu a parcela de corte do MMLU-Pro de 28,9% para 0,5% nos logs dos autores; veja o final deste arquivo). Use-a apenas se o usuário concordar.
Nunca use resultados separados para escolher; apenas as verificações práticas decidem.
10. Teste separado a partir da cópia (30–60 min). Inicie-o quando a execução base separada passar de suas respostas mais longas:
python $SKILL/scripts/run_eval.py --task $T --model $COPY --label copy --data $WORK/splits/heldout.jsonl --ids-file $WORK/splits/test_ids.txt --out $WORK/test/copy.jsonl --jobs 2
python $SKILL/scripts/grade.py --task $T $G $WORK/test/base.jsonl $WORK/test/copy.jsonl --out $WORK/test/summary.json
Dê o veredito do resultado pareado:
- "Ganho claro" quando corrigido > quebrado e p do teste de sinal < 0,05.
- "Consistente com um ganho, não comprovado" quando corrigido > quebrado, mas p ≥ 0,05; recomende um teste maior.
- "Nenhum ganho" quando corrigido ≤ quebrado; recomende restaurar a cópia.
11. Relatório com o modelo em references/evaluation.md:
- a pontuação separada antes e depois, e as contagens pareadas corrigido/quebrado;
- as taxas de comportamento (corte, sinalizado, pensamento fechado);
- cada candidato com seus números de filtro e verificação, e o controle aleatório;
- os pesos alterados;
- a ressalva: desligar um hábito não é uma nova habilidade.
Erros que custam horas antes (não repita)
- Lote muito grande, ou dois trabalhos longos em seus picos. Uma falha de memória mata as respostas em andamento, que são os loops. Use
--batch auto --jobs 2, e nunca inicie um segundo trabalho de 32k enquanto o primeiro estiver em suas respostas mais longas. O supervisor reinicia execuções travadas com metade do lote. - Esperar por execuções inteiras. Cerca de 15 loops são suficientes.
- Edições na memória para avaliação. Apenas
probe.py screenaplica edições na memória, e apenas para leituras forçadas pelo professor. - Confiar apenas na triagem. Uma queda de triagem de −0,10 em loops quebrou apenas 3 de 26 loops na geração, então sempre execute o portão.
- Avaliar uma execução parcial, ou escolher perguntas de teste por qual terminou primeiro.
- Parar trabalhos com
pkill -f <texto>, o que também mata observadores cuja linha de comando contém aquele texto.
Regras (como no post)
Mantenha estas e diga no relatório que o fez.
- Sem treinamento. Sem passes para trás, autograd, otimizadores ou ajuste iterativo de pesos a uma perda. Ajustar α ou pesos até que uma métrica melhore conta como ajuste. Escolher entre a lista fixa de candidatos acima não conta.
- Passes para frente para medição são permitidos: geração, probabilidades logarítmicas forçadas pelo professor, estados ocultos e ativações do MLP, e estatísticas destes ou dos pesos (médias, diferenças, SVD).
- Fora do escopo:
- as embeddings, a cabeça de saída (empatada) e a norma final (
weights.pyos recusa); - multiplicar um tensor inteiro por uma constante;
- nitidez ou achatamento global;
- forçar o pensamento a fechar cedo, independentemente do problema;
- remover comportamento de segurança: recusas, salvaguardas, conformidade com solicitações prejudiciais.
- as embeddings, a cabeça de saída (empatada) e a norma final (
- Apenas os valores dos pesos mudam. Mantenha nomes, formas, dtype, configuração, tokenizador, modelo de chat e configurações de decodificação.
- Disciplina de divisão. Conjuntos de prática e separados disjuntos, fixados antes de qualquer edição. Olhe para os resultados separados uma vez, após a edição estar congelada.
- Seleção divulgada. O relatório lista cada candidato tentado e a métrica que escolheu o vencedor.
Por que cada etapa é como é
- Contraste: a direção é a diferença média, na saída de cada camada, entre texto indesejado e desejado que é correspondentemente combinado.
- Para loops,
loops.pypega os 1.024 tokens logo antes do início do loop e 1.024 tokens dentro dele: mesma pergunta, mesma resposta, quase a mesma posição. O início é o primeiro ponto onde 90% dos próximos 512 tokens repetem um 16-grama anterior. - Para outros comportamentos, os pares fornecem a correspondência.
- Para loops,
- Colunas do escritor: score_j = (média indesejada − média desejada de ativação do neurônio j) × (u · W_down[:, j]); pegue os k principais. A escrita estática u·W sozinha não é suficiente: apenas 21–22 das 64 colunas publicadas por camada estão em seus 64 principais.
- A edição: W'[:, S] = W[:, S] − α u (uᵀ W[:, S]), computado em float32 a partir do modelo sendo editado. α = 1 remove o componente, o que não fez nada para loops; α = 6 o inverte para −5c, então esses neurônios escrevem contra o comportamento.
- Triagem: a mudança na probabilidade logarítmica média com a edição aplicada. Texto indesejado deve se tornar menos provável; texto desejado e limpo deve permanecer. Uma direção aleatória com norma correspondente nas mesmas colunas é o controle.
- O portão: os números da triagem preveem a geração apenas vagamente (veja os resultados abaixo), então uma verificação de geração curta em perguntas práticas decide antes do teste separado.
- Salvando:
weights.pyreescreve apenas os fragmentos safetensors tocados a partir dos arquivos brutos. Os motores de inferência reescrevem ou descartam tensores no carregamento, então nunca salve pesos de um. - Protocolo: pensamento ligado, temperatura 0,6, top_p 0,95, top_k 20, penalidade de presença 1,5 apenas em tokens gerados, 32.768 novos tokens (
--profile thinking). Todos os motores correspondem à semântica do vLLM. - Pareamento: motores e tamanhos de lote alteram os números aleatórios, então pareie por pergunta dentro de uma comparação. Nunca compare pontuações medidas em perguntas diferentes ou com motores diferentes.
R
...
[truncado para tradução]