Skip to content

Repository files navigation

Mahjong AI — Riichi Mahjong (4-player) Model

中文文档

A competitive Riichi Mahjong AI trained via offline reinforcement learning (Conservative Q-Learning) on expert-level game records.

Model

Architecture: 1D CNN with channel attention

  • 192 channels, 40 residual blocks
  • Based on the Mortal v4 observation encoder
  • Auxiliary heads for multi-task learning

Training method: CQL (Conservative Q-Learning) with auxiliary supervision

  • Primary: DQN action-value + CQL regularization + next-rank prediction
  • Auxiliary heads: score prediction, rank prediction (4-player), score-gap prediction
  • Curriculum: initial training on top ~250 players, then expanded to top ~750 players

Training data:

  • ~1.38 million games from ~750 top-level 4-player Riichi Mahjong players
  • East+South (半荘) format, competitive lobby level

Hyperparameters

ParameterValue
conv_channels192
num_blocks40
batch_size256
lr_peak1e-4
lr_final1e-5
warmup_steps200
weight_decay0.1
max_grad_norm1.0
gamma1.0
min_q_weight (CQL)5.0
next_rank_weight0.2
score_weight1.0
rank_weight0.5
gap_weight0.3
DDP2× GPU

Performance

Tested over 4000 games (random seating, East+South):

ModelAvg Rank1st %2nd %3rd %4th %
v4 (baseline)2.41927.326.223.722.8
c3 (ours)2.49225.524.525.224.8

The model approaches but does not yet surpass the Mortal v4 baseline. Training is ongoing.

Files

weights/ # Download from Releases
model-c3-best.pth # Main model checkpoint (132MB)
grp-best.pth # GRP (Game Result Predictor) network (2.2MB)
scripts/
train_main.py # Training script (CQL + auxiliary heads)
dataloader.py # Data loader (reads .mjson via libriichi)
mortal_bot_server.py # Inference bridge (connects model to game engine)
verify_worker_http.sh # Distributed verification worker
mahjong/ # Game engine + AI controller (TypeScript)
src/ # Engine source (game logic, shanten, scoring, AI)
scripts/verify.ts # Verification orchestrator
scripts/verify-worker.ts # Per-worker game runner
package.json
cf-verify/ # Cloudflare Worker coordinator for distributed testing

Download weights

Download from Releases:

mkdir -p weights
curl -L -o weights/model-c3-best.pth https://github.com/lynkas/c3/releases/download/v1.0/model-c3-best.pth
curl -L -o weights/grp-best.pth https://github.com/lynkas/c3/releases/download/v1.0/grp-best.pth

Usage

Loading the model for inference

importtorchimportsyssys.path.insert(0, "path/to/mortal/mortal")
frommodelimportBrain# Load checkpointckpt=torch.load("weights/model-c3-best.pth", map_location="cpu")
model=Brain(version=4, conv_channels=192, num_blocks=40)
model.load_state_dict(ckpt["mortal"])
model.eval()

Training

Requires: Mortal's libriichi compiled, a Python venv with PyTorch, and game data in .mjson format.

# Single GPU
python scripts/train_main.py \
--grp checkpoints/grp-best.pth \
--train-glob "data/train/*.mjson" \
--val-glob "data/val/*.mjson" \
--save checkpoints/model.pth \
--tensorboard runs/experiment \
--device cuda \
--conv-channels 192 --num-blocks 40 \
--batch-size 256 \
--lr-peak 1e-4 --lr-final 1e-5 \
--warmup-steps 200 --max-steps 200000 \
--save-every 400 --val-steps 50 --patience 100 \
--weight-decay 0.1 --max-grad-norm 1.0 \
--score-weight 1.0 --rank-weight 0.5 --gap-weight 0.3
# Multi-GPU (DDP)
CUDA_VISIBLE_DEVICES=0,1 torchrun --standalone --nproc_per_node=2 scripts/train_main.py \
[same args as above]

Key arguments:

ArgumentDescription
--grpPath to GRP (Game Result Predictor) checkpoint
--train-globGlob pattern for training data files
--val-globGlob pattern for validation data files
--saveOutput checkpoint path (also used for resume)
--patienceEarly stopping patience (in validation cycles)
--score-weightWeight for score prediction auxiliary loss
--rank-weightWeight for rank prediction auxiliary loss
--gap-weightWeight for gap prediction auxiliary loss

To resume training, simply point --save to an existing checkpoint.

Distributed Verification System

The verification system runs model-vs-model games across multiple machines, coordinated by a Cloudflare Worker.

1. Deploy the coordinator

cd cf-verify
npm install
# Edit wrangler.toml with your account_id, KV namespace, and tokens
npx wrangler deploy

2. Create a job

curl -X POST "https://your-worker.workers.dev/job" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-d '{ "total": 1000, "strategies": "mortal:model-a.pth,mortal:model-b.pth,mortal:model-c.pth,mortal:model-d.pth", "difficulties": "0,0,0,0", "end_round": 8, "shuffle_seats": true }'

3. Start workers

Each worker claims batches, runs games locally, and reports results back.

COORDINATOR="https://your-worker.workers.dev" \
TOKEN="YOUR_WORKER_TOKEN" \
WORKER_NAME="my-machine" \
WORKERS=2 \
MODELS_DIR="./weights" \
DEVICE_MAP="cuda:0,cuda:0,cuda:0,cuda:0" \
MAHJONG_DIR="/path/to/mahjong" \
MORTAL_PYTHON="/path/to/venv/bin/python3" \
MORTAL_SERVER="/path/to/mortal_bot_server.py" \
bash scripts/verify_worker_http.sh

Worker environment variables:

VariableRequiredDescription
COORDINATORCoordinator URL
TOKENWorker auth token
WORKER_NAMEIdentifier shown on dashboard
WORKERSParallel game workers (default: 4)
MODELS_DIRDirectory containing model .pth files
DEVICE_MAPComma-separated devices for each strategy (e.g. cuda:0,cuda:0,cuda:1,cuda:1)
MAHJONG_DIRPath to mahjong project (with scripts/verify.ts)
MORTAL_PYTHONPython interpreter with torch + libriichi
MORTAL_SERVERPath to mortal_bot_server.py

4. Monitor progress

  • Dashboard: Visit the coordinator URL in a browser
  • API:
    curl https://your-worker.workers.dev/status # Job progress + worker status
    curl https://your-worker.workers.dev/aggregate # Overall rankings
    curl https://your-worker.workers.dev/aggregate?worker=my-machine # Per-worker stats

5. Manage jobs

# Extend a job's target
curl -X PATCH "https://your-worker.workers.dev/job" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-d '{"job_id": "abc123", "total": 4000, "set_current": true}'# List all jobs
curl https://your-worker.workers.dev/jobs

Auxiliary Heads

The model uses multi-task learning with three auxiliary prediction heads that share the backbone:

  1. ScoreHead — Predicts current 4-player scores (MSE loss)
  2. RankHead — Predicts rank distribution of all 4 players (Cross-entropy)
  3. GapHead — Predicts log-scale score gap to top/bottom player (Huber loss)

These heads force the backbone to internalize game-state awareness (score positions, rank dynamics) that the raw observation encoding makes difficult to learn through policy gradients alone.

Training Pipeline

The model is trained in stages (curriculum learning):

Stage 1: Base model (c)

Starting from Mortal v4 pre-trained weights, fine-tune with CQL on top ~242 players:

python scripts/train_main.py \
--grp checkpoints/grp-best.pth \
--train-glob "data/top_players_242/*.mjson" \
--val-glob "data/val/*.mjson" \
--save checkpoints/model-c.pth \
--lr-peak 3e-5 --lr-final 1e-6 \
--weight-decay 0.2 --score-weight 1.0 \
--rank-weight 0 --gap-weight 0

Stage 2: Expand data (c2)

From model-c, continue training on ~750 players:

cp checkpoints/model-c-best.pth checkpoints/model-c2.pth
python scripts/train_main.py \
--save checkpoints/model-c2.pth \
--train-glob "data/top_players_750/*.mjson" \
--lr-peak 3e-5 --lr-final 1e-6 \
--weight-decay 0.2 --score-weight 1.0 \
--rank-weight 0 --gap-weight 0

Stage 3: Add auxiliary heads (c3)

From model-c2, add rank/gap prediction heads with higher learning rate:

cp checkpoints/model-c2-best.pth checkpoints/model-c3.pth
python scripts/train_main.py \
--save checkpoints/model-c3.pth \
--train-glob "data/top_players_750/*.mjson" \
--lr-peak 1e-4 --lr-final 1e-5 \
--weight-decay 0.1 \
--score-weight 1.0 --rank-weight 0.5 --gap-weight 0.3

c3-best was achieved after ~14,400 steps of stage 3 training.

Data format

Training data uses .mjson format (gzipped JSON lines), where each file contains one game in mjai event format. The dataloader (scripts/dataloader.py) handles parsing via libriichi.

Requirements

  • Python 3.10+
  • PyTorch 2.0+
  • libriichi (Rust-compiled Python extension)
  • Node.js + tsx (for verification scripts)

License

MIT

Model weights, training code, and verification tools are released under the MIT License.

Note: The runtime inference environment requires Mortal (AGPL-3.0) components (libriichi, observation encoder). This repository does not include Mortal source code.

Acknowledgments

Built on the Mortal framework by Equim.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages