Segment Token Training Guide

April 14, 2026 · View on GitHub

This guide walks through training BPE segment tokens from an SVG corpus using the HiVG tokenizer. Segment tokens are the key to HiVG's 2.76x token compression — they merge frequently co-occurring SVG command sequences into single tokens.

Background

HiVG uses a three-level tokenization hierarchy:

LevelNameExample
0Raw SVGM 100 200 c -5 0 -10 5 -10 10
1Atomic tokens<cmd_M><P_100><P_200><cmd_c><d_-5><d_0><d_-10><d_5><d_-10><d_10>
2Segment tokens<cmd_M><P_100><P_200><SEG_42>

Level 2 is learned by running semantic BPE on Level 1 sequences. Unlike standard BPE, this variant only merges complete path command segments (e.g., a full cubic Bézier <cmd_c> + 6 coordinates), so every learned merge is a valid SVG operation.

Prerequisites

pip install -e .

All required dependencies (pyyaml, tqdm, etc.) are included in setup.py.

Step 1 — Prepare Atomic Token Data

Start with a JSONL file where each line contains SVG content encoded as Level 1 atomic tokens. Two formats are supported:

Alpaca format (text2svg):

{"instruction": "Draw a red circle", "input": "", "output": "<svg><circle>...</circle></svg>"}

ShareGPT format (img2svg):

{"messages": [{"role": "user", "content": "<image>Convert to SVG"}, {"role": "assistant", "content": "<svg><path>...</path></svg>"}], "images": ["img.png"]}

The output field (Alpaca) or last assistant message (ShareGPT) should contain the atomic token string.

Tokenizing raw SVG

If you have raw SVG files, tokenize them first:

from hivg_tokenizer import SVGTokenizer, tokens_to_string

tokenizer = SVGTokenizer(coord_range=234)

with open("example.svg") as f:
    svg_string = f.read()

tokens = tokenizer.tokenize(svg_string)
token_str = tokens_to_string(tokens)  # compact string representation
print(token_str)
# '<svg><path><cmd_M><P_100><P_200><cmd_c><d_-5><d_0>...'

Step 2 — Train BPE Segments

2.1 Load training data

import json
from hivg_tokenizer import SVGBPETrainer, string_to_tokens

# Collect atomic token sequences from your JSONL corpus
token_sequences = []
with open("train_data.jsonl") as f:
    for line in f:
        record = json.loads(line)
        atomic_str = record.get("output", "")  # Alpaca format
        if atomic_str:
            token_sequences.append(string_to_tokens(atomic_str))

print(f"Loaded {len(token_sequences)} sequences")

2.2 Train

trainer = SVGBPETrainer(
    coord_range=234,       # must match your tokenizer config
    min_frequency=50,      # minimum pair frequency to learn a merge
)

segments = trainer.train(
    token_sequences,
    num_merges=500,        # number of BPE merge operations
    verbose=True,
)

Training output:

Extracting complete segments...
Extracted 4821630 complete segments from 120000 sequences
Starting BPE training (max 500 merges, min_freq=50)...
Unique segments: 28451
Unique pairs: 193720
  Merge 50: freq=12340, <cmd_l> <d_0> <d_-1>... (3 tokens)
  Merge 100: freq=8210, <cmd_c> <d_0> <d_3> <d_-2> <d_5> <d_-2> <d_3>
  ...
Training complete. Learned 500 segments.

2.3 Inspect results

trainer.print_top_segments(n=20)
Top 20 Learned Segments (complete commands only):
================================================================================
ID     Token        Freq     Original Sequence
--------------------------------------------------------------------------------
42     <SEG_42>     18320    <cmd_c> <d_0> <d_3> <d_-2> <d_5> <d_-2> <d_3>
17     <SEG_17>     15890    <cmd_l> <d_0> <d_-1>
...

2.4 Save the model

trainer.save("bpe_model.json")

The JSON file contains all merge rules and segment definitions, and can be loaded later:

trainer = SVGBPETrainer.load("bpe_model.json")

Step 3 — Encode & Decode

Single sequence

from hivg_tokenizer import string_to_tokens, tokens_to_string

trainer = SVGBPETrainer.load("bpe_model.json")

atomic_tokens = string_to_tokens("<svg><path><cmd_M><P_50><P_50><cmd_c><d_0><d_3>...")
segment_tokens = trainer.encode(atomic_tokens)  # Level 1 → Level 2

print(f"Atomic:  {len(atomic_tokens)} tokens")
print(f"Segment: {len(segment_tokens)} tokens")
print(f"Compression: {len(atomic_tokens) / len(segment_tokens):.2f}x")

# Decode back to atomic tokens
recovered = trainer.decode(segment_tokens)
assert recovered == atomic_tokens

Batch JSONL conversion

The segment_io module provides utilities for converting entire JSONL datasets:

from hivg_tokenizer.bpe import SVGBPETrainer
from hivg_tokenizer.segment_io import (
    convert_jsonl_file,
    encode_text2svg_record,
    encode_image2svg_record,
)

trainer = SVGBPETrainer.load("bpe_model.json")

# Text-to-SVG dataset (Alpaca format)
stats = convert_jsonl_file(
    input_path="train_text2svg.jsonl",
    output_path="train_text2svg_segment.jsonl",
    transform_record=encode_text2svg_record,
    bpe=trainer,
    task_name="text2svg",
)
print(f"Written: {stats['written']} records")
print(f"Compression: {stats['compression']:.1f}%")

# Image-to-SVG dataset (ShareGPT format)
stats = convert_jsonl_file(
    input_path="train_img2svg.jsonl",
    output_path="train_img2svg_segment.jsonl",
    transform_record=encode_image2svg_record,
    bpe=trainer,
    task_name="img2svg",
)

Conversion options

ParameterDefaultDescription
target_recordsNoneStop after N records (useful for quick tests)
require_segment_matchFalseSkip records where no segment token was applied
flush_every2000Flush output file every N records
retries5Retry count on stale file handle (NFS/FUSE)

Configuration Reference

Pre-defined configs are located in hivg_tokenizer/configs/:

ConfigCanvascoord_rangerelative_rangeBPE mergesMin freq
config_224.yaml22423423450050
config_784.yaml7847947941000100
config_1008.yaml10241008100850010

Loading a config:

import yaml

with open("hivg_tokenizer/configs/config_224.yaml") as f:
    cfg = yaml.safe_load(f)

trainer = SVGBPETrainer(
    coord_range=cfg["coord_range"],
    relative_range=cfg["relative_range"],
    min_frequency=cfg["bpe"]["min_frequency"],
)

Large Dataset Training

For datasets exceeding ~1.5M samples, the built-in guardrail in training_limits.py automatically caps the training set to 1.2M samples. This is sufficient to learn dominant SVG patterns without exhausting memory.

To override:

from hivg_tokenizer.training_limits import choose_bpe_sample_plan

plan = choose_bpe_sample_plan(
    total_samples=3_000_000,
    requested_max_samples=None,
    force_large_run=True,         # bypass the auto-cap
)

End-to-End Example

import json
from hivg_tokenizer import SVGTokenizer, SVGBPETrainer, string_to_tokens, tokens_to_string

# 1. Tokenize raw SVGs to atomic tokens
tokenizer = SVGTokenizer(coord_range=234)
token_sequences = []

for svg_file in svg_files:
    with open(svg_file) as f:
        tokens = tokenizer.tokenize(f.read())
        token_sequences.append(tokens)

# 2. Train BPE
trainer = SVGBPETrainer(coord_range=234, min_frequency=50)
trainer.train(token_sequences, num_merges=500)
trainer.save("bpe_model.json")

# 3. Encode a sequence
atomic = token_sequences[0]
segment = trainer.encode(atomic)
print(f"{len(atomic)}{len(segment)} tokens ({len(atomic)/len(segment):.2f}x)")

# 4. Verify round-trip
assert trainer.decode(segment) == atomic

FAQ

Q: How many training samples do I need? A: 50k–200k SVG samples are typically enough to learn stable segment patterns. More data improves coverage of rare command combinations.

Q: How do I choose num_merges? A: 500 merges is a good default. More merges give higher compression but add more special tokens to the LLM vocabulary. The sweet spot depends on your corpus diversity.

Q: Should coord_range match at training and inference time? A: Yes. The BPE model is trained with a specific coord_range, and the same value must be used during encoding, decoding, and inference. A mismatch will produce incorrect SVG output.

Q: Can I merge segment models trained on different datasets? A: No. Each BPE model's segment IDs are assigned sequentially during training. Use a single training run on a combined corpus instead.