Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

17 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Project TaH Logo

Think-at-Hard

Selective Latent Iterations to Improve Reasoning Language Models

🌐 Project Page β€’ πŸ“‘ Paper β€’ πŸ€— HuggingFace

Think-at-Hard (TaH) improves LLM reasoning by running extra latent iterations only on hard tokens instead of all tokens. A lightweight decider and duo-causal attention enable targeted refinement while keeping full parallelism. TaH outperforms fixed two-iteration baselines by 8–11% while skipping 94% of second iterations, and also beats strong single-iteration Qwen3 models by 4–5%.

Feel free to star the repo or cite the paper if you find it interesting.

@article{fu2025tah,
    title={Think-at-Hard: Selective Latent Iterations to Improve Reasoning Language Models}, 
    author={Tianyu Fu and Yichen You and Zekai Chen and Guohao Dai and Huazhong Yang and Yu Wang},
    journal={arXiv preprint arXiv:2511.08577},
    year={2025},
}

News

Environment Setup

Create a new environment:

conda create -n tah python=3.10
conda activate tah

Install the package:

pip install -e .

For training and evaluation, install additional dependencies:

pip install -e ".[training,evaluation]"

For code generation evaluation, install evalplus

Note if you git pull and the top-level package layout changes (e.g. __init__.py is added or removed), re-run pip install -e . β€” the editable install caches the layout in site-packages/__editable___tah_*_finder.py and stale state will silently drop tah/__init__.py's re-exports.

Run an example for TaH

python script/playground/inference_example.py                       # quick demo (~1 min)
python script/playground/inference_example.py --max-new-tokens 16384 # full reasoning chain

This script demonstrates TaH's selective latent iteration mechanism, with color-coded output showing the iteration count for each token.

Run evaluation

Evaluate TaH model

python script/evaluation/eval.py \
    --eval_config ./script/recipes/qwen3_1.7/eval_tah.yaml \
    --model_path nics-efc/TaH-plus-1.7B \
    --dataset_name gsm8k \
    --backend tah \
    --job_nums 8 \
    --tp_size_per_job 1

Key parameters:

  • --eval_config: Path to evaluation config file
  • --model_path: Path to the model
  • --dataset_name: Dataset name (supports gsm8k, math500, aime24, etc. Detailed configs can be found in tah/evaluate/eval_configs/dataset_configs.json)
  • --backend: Inference backend (tah for TaH)
  • --job_nums: Number of parallel jobs (one job pins tp_size_per_job GPUs)
  • --tp_size_per_job: Tensor parallel size per job
  • --data_range N / --data_range start end: subset slice β€” handy for smoke tests
  • --data_ids gsm8k_0,gsm8k_5: run only specific problem ids

Single-GPU smoke

The default recipe targets 8 GPUs (--job_nums 8). To sanity-check the pipeline on one GPU in a couple of minutes, slice the dataset and shrink max_new_tokens:

# clone the recipe and shrink generation length
sed 's/max_new_tokens: 4096/max_new_tokens: 512/' \
    script/recipes/qwen3_1.7/eval_tah.yaml > /tmp/eval_tah_smoke.yaml

CUDA_VISIBLE_DEVICES=0 python script/evaluation/eval.py \
    --eval_config /tmp/eval_tah_smoke.yaml \
    --model_path nics-efc/TaH-plus-1.7B \
    --dataset_name gsm8k --backend tah \
    --job_nums 1 --tp_size_per_job 1 \
    --data_range 5 \
    --output_dir /tmp/tah_eval_smoke

The TaH backend is a token-by-token Python loop intended for research; for serving throughput, use --backend sglang or the dedicated minisgl-tah server.

Evaluate with a different backend

The same script/evaluation/eval.py accepts --backend hf (vanilla AutoModelForCausalLM.generate β€” useful for non-TaH baselines) or --backend sglang (sgl Engine for high-throughput serving). All three backends share the same job-sharded driver under tah/evaluate/jobs.py:allocate_gpus_and_run_jobs.

Train your own TaH model

Training a TaH model consists of three stages:

Step0: Prepare model and data

1. Prepare training data

Use a reference model to generate hard token labels for the training and validation data:

# download the default subset of OpenR1-Math-220k
python script/preparation/download.py
# filter and split
python script/preparation/filter_split.py
# label the hard tokens
python script/preparation/label.py \
    --num_gpu 8 \
    --dataset_path ./data/initial_data/openr1-math/train.jsonl \
    --test_model_list Qwen/Qwen3-1.7B \
    --output_path ./data/processed_data/openr1-math/1_7/train \
    --max_input_length 10000
python script/preparation/label.py \
    --num_gpu 8 \
    --dataset_path ./data/initial_data/openr1-math/eval.jsonl \
    --test_model_list Qwen/Qwen3-1.7B \
    --output_path ./data/processed_data/openr1-math/1_7/eval \
    --max_input_length 10000 \

2. (Optional) Prepare pruned model

For the TaH version, prune one layer from the base model to match the parameter count of the standard baseline (skip this step for TaH+ version):

python script/preparation/prune.py \
    --model Qwen/Qwen3-1.7B-Base \
    --dataset ./data/processed_data/openr1-math/1_7/eval \
    --output ./model/qwen3_1.7_base_pruned \
    --num_prune 1

Step1: Train with Fixed Iteration Labels

The first stage uses fixed iteration labels for training:

python -m accelerate.commands.launch \
    --config_file ./script/recipes/accelerate_configs/zero2.yaml \
    --num_processes 8 \
    ./script/train/SFT_TaH.py \
    --config ./script/recipes/qwen3_1.7/sft_tah_step1.yaml

Key configurations in Step1 (sft_tah_step1.yaml):

  • max_iter: 2 β€” maximum number of iterations.
  • iter_decider: "IterLabelDecider" β€” continue iff the per-token oracle iter_count_labels (derived from mismatch) say so. Used to teach the LoRA adapter on tokens marked "hard" by the labeller.
  • adapter: "lora" β€” only LoRA is supported in tah-release.
  • train_loss: "NextTokenPredLoss" β€” standard causal-LM cross-entropy.

Single-implementation hooks (input/output updaters, iter labels, adapter) are inlined into the wrapper β€” only iter_decider and train_loss are config-selectable.

Step2: Train Iteration Decider

The second stage trains the iteration decider:

python -m accelerate.commands.launch \
    --config_file ./script/recipes/accelerate_configs/zero2.yaml \
    --num_processes 8 \
    ./script/train/SFT_TaH.py \
    --config ./script/recipes/qwen3_1.7/sft_tah_step2.yaml

Key configurations in Step2 (sft_tah_step2.yaml):

  • tah_model_path: Load the model trained in Step1
  • iter_decider: "MLPIterDecider": Use MLP decider to automatically determine iterations
  • train_loss: "IterDeciderLoss": Iteration decider loss function
  • freeze_component: [model.simple_base_model]: Freeze model backbone

After two-stage training, the model can automatically decide when to perform latent reasoning iterations.

Understand the Code

Code Structure

TaH/
β”œβ”€β”€ tah/
β”‚   β”œβ”€β”€ model/                     # core model
β”‚   β”‚   β”œβ”€β”€ tah_model.py           # TaHForCausalLM wrapper + inlined slot helpers
β”‚   β”‚   β”œβ”€β”€ iter_decider.py        # IterLabelDecider, MLPIterDecider, _BY_NAME
β”‚   β”‚   β”œβ”€β”€ loss.py                # NextTokenPredLoss, IterDeciderLoss, _BY_NAME
β”‚   β”‚   β”œβ”€β”€ causal_cache.py        # TaHCache: per-(layer, iter) KV
β”‚   β”‚   β”œβ”€β”€ tah_config.py          # @dataclass TaHConfig
β”‚   β”‚   └── utils.py               # generation helper, IterCountColors
β”‚   β”œβ”€β”€ train/                     # HF Trainer subclass + collator + iter-aware callback
β”‚   β”œβ”€β”€ evaluate/                  # multi-backend eval driver
β”‚   β”‚   β”œβ”€β”€ datasets.py            # benchmark loading + standardisation
β”‚   β”‚   β”œβ”€β”€ backends.py            # sglang / hf / tah model + inference fn
β”‚   β”‚   β”œβ”€β”€ jobs.py                # job-sharded runner + result aggregation
β”‚   β”‚   β”œβ”€β”€ matheval.py            # math benchmark graders (math_verify)
β”‚   β”‚   └── codeeval.py            # humaneval / mbpp via evalplus
β”‚   └── utils/                     # SFT preprocessing
β”œβ”€β”€ script/
β”‚   β”œβ”€β”€ preparation/               # download.py, label.py, prune.py, filter_split.py
β”‚   β”œβ”€β”€ train/SFT_TaH.py           # SFT entrypoint
β”‚   β”œβ”€β”€ evaluation/eval.py         # eval CLI entrypoint
β”‚   β”œβ”€β”€ playground/                # inference demo
β”‚   └── recipes/qwen3_{0.6,1.7}/   # training + eval YAML recipes
└── pyproject.toml

Related Projects

Explore more efficient LLM projects from us:

R2R
Token-level routing for reasoning LLMs
C2C
Communicate through KV-Cache between LLMs
FrF
Efficient video token reduction for LVLMs
MoA
Mixture of sparse attention for LLMs

About

[ICML'26] Official implementation of paper "Think-at-Hard: Selective Latent Iterations to Improve Reasoning Language Models"

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages