Add script to download skypile data
parent
2ce02ea4ab
commit
b59f54cfc5
|
|
@ -0,0 +1,163 @@
|
||||||
|
"""
|
||||||
|
Streaming 下载 SkyPile-150B 子采样,转成 minimind PretrainDataset 兼容格式({"text": ...})。
|
||||||
|
|
||||||
|
特性:
|
||||||
|
- Streaming:不全下,边读边写,磁盘只占目标量
|
||||||
|
- Token 计数:精确控制下载量(按 budget 而非样本数停)
|
||||||
|
- 断点续传:进程被杀也能从最后写到的行接着下
|
||||||
|
- 进度日志:每 N 个样本打印进度
|
||||||
|
- 简易过滤:太短 / 重复字符多的样本跳过
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
# 默认下 25B tokens(1B 模型 Chinchilla 最优)
|
||||||
|
python download_skypile.py
|
||||||
|
|
||||||
|
# 改下载量
|
||||||
|
python download_skypile.py --target_tokens 10_000_000_000
|
||||||
|
|
||||||
|
# 改输出路径
|
||||||
|
python download_skypile.py --output ../dataset/skypile_25b.jsonl
|
||||||
|
|
||||||
|
需要 fwdproxy(HuggingFace 在 allowlist 但 OD 无直接 DNS):
|
||||||
|
export https_proxy=http://fwdproxy:8080
|
||||||
|
export http_proxy=http://fwdproxy:8080
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
__package__ = "scripts"
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import warnings
|
||||||
|
from datasets import load_dataset
|
||||||
|
from transformers import AutoTokenizer
|
||||||
|
|
||||||
|
warnings.filterwarnings('ignore')
|
||||||
|
|
||||||
|
|
||||||
|
def count_tokens_fast(text: str) -> int:
|
||||||
|
"""快速估算 token 数:中文按字符 ≈ token,英文按词 × 1.3。
|
||||||
|
用于在线计数(精确 tokenize 太慢)。"""
|
||||||
|
n_chars = len(text)
|
||||||
|
# 中文 token ≈ 1 char;英文 1 token ≈ 4 char
|
||||||
|
# 简单估算:取个折中
|
||||||
|
return int(n_chars / 1.8)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Stream-download SkyPile-150B subset")
|
||||||
|
parser.add_argument('--repo', default='Skywork/SkyPile-150B', type=str)
|
||||||
|
parser.add_argument('--split', default='train', type=str)
|
||||||
|
parser.add_argument('--output', default='../dataset/skypile_25b.jsonl', type=str)
|
||||||
|
parser.add_argument('--target_tokens', type=int, default=25_000_000_000,
|
||||||
|
help='目标 token 数(estimate),到达后停止')
|
||||||
|
parser.add_argument('--min_length', type=int, default=200,
|
||||||
|
help='样本最小字符数,太短跳过')
|
||||||
|
parser.add_argument('--log_every', type=int, default=5000)
|
||||||
|
parser.add_argument('--tokenizer_path', type=str, default='../model',
|
||||||
|
help='用 minimind tokenizer 精确计算 token 数(每 N 个样本核对一次)')
|
||||||
|
parser.add_argument('--exact_count_every', type=int, default=50000,
|
||||||
|
help='每 N 个样本精确 tokenize 一次校准估算误差')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
output_path = os.path.abspath(args.output)
|
||||||
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||||
|
|
||||||
|
# 断点续传:count 已有文件的行数
|
||||||
|
n_written, est_tokens_written = 0, 0
|
||||||
|
if os.path.exists(output_path):
|
||||||
|
print(f'[resume] found existing {output_path}, counting...')
|
||||||
|
with open(output_path) as f:
|
||||||
|
for line in f:
|
||||||
|
n_written += 1
|
||||||
|
try:
|
||||||
|
est_tokens_written += count_tokens_fast(json.loads(line)['text'])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
print(f'[resume] {n_written:,} samples already written, ~{est_tokens_written/1e9:.2f}B tokens')
|
||||||
|
if est_tokens_written >= args.target_tokens:
|
||||||
|
print('[resume] target already reached, exit')
|
||||||
|
return
|
||||||
|
|
||||||
|
# 校准 tokenizer(可选;下载完后跑 eval_perplexity 时会用到)
|
||||||
|
tokenizer = None
|
||||||
|
if args.tokenizer_path:
|
||||||
|
try:
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(args.tokenizer_path)
|
||||||
|
print(f'[tokenizer] loaded for calibration: vocab={tokenizer.vocab_size}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[tokenizer] failed to load ({e}), will skip calibration')
|
||||||
|
|
||||||
|
print(f'[start] streaming {args.repo} split={args.split} → {output_path}')
|
||||||
|
print(f'[target] {args.target_tokens/1e9:.1f}B tokens (estimated)')
|
||||||
|
|
||||||
|
ds = load_dataset(args.repo, split=args.split, streaming=True)
|
||||||
|
|
||||||
|
skipped_short = 0
|
||||||
|
n_seen = 0
|
||||||
|
t0 = time.time()
|
||||||
|
calibration_ratio = 1.0 # actual_tokens / estimated_tokens,每次校准更新
|
||||||
|
|
||||||
|
with open(output_path, 'a') as fout:
|
||||||
|
for sample in ds:
|
||||||
|
n_seen += 1
|
||||||
|
# 跳过已写过的(断点续传)
|
||||||
|
if n_seen <= n_written:
|
||||||
|
continue
|
||||||
|
|
||||||
|
text = sample.get('text', '')
|
||||||
|
if len(text) < args.min_length:
|
||||||
|
skipped_short += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
est_tok = count_tokens_fast(text)
|
||||||
|
actual_est = int(est_tok * calibration_ratio)
|
||||||
|
|
||||||
|
# 写入 minimind 格式
|
||||||
|
fout.write(json.dumps({'text': text}, ensure_ascii=False) + '\n')
|
||||||
|
n_written += 1
|
||||||
|
est_tokens_written += actual_est
|
||||||
|
|
||||||
|
# 进度日志
|
||||||
|
if n_written % args.log_every == 0:
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
rate = (n_written / elapsed) if elapsed > 0 else 0
|
||||||
|
progress = est_tokens_written / args.target_tokens * 100
|
||||||
|
eta_h = (args.target_tokens - est_tokens_written) / max(est_tokens_written / elapsed, 1) / 3600
|
||||||
|
print(f'[{n_written:>9,} samples] {est_tokens_written/1e9:>5.2f}B tok '
|
||||||
|
f'({progress:>5.1f}%) | {rate:.0f} samples/s | ETA {eta_h:.1f}h | '
|
||||||
|
f'skipped_short={skipped_short:,}')
|
||||||
|
fout.flush()
|
||||||
|
|
||||||
|
# 周期性精确 tokenize 几条校准估算
|
||||||
|
if tokenizer and n_written % args.exact_count_every == 0:
|
||||||
|
actual = sum(len(tokenizer(t, add_special_tokens=False).input_ids)
|
||||||
|
for t in [text])
|
||||||
|
estimate = est_tok
|
||||||
|
if estimate > 0:
|
||||||
|
new_ratio = actual / estimate
|
||||||
|
calibration_ratio = 0.9 * calibration_ratio + 0.1 * new_ratio
|
||||||
|
print(f'[calibration] est_ratio updated → {calibration_ratio:.3f} '
|
||||||
|
f'(this sample: est={estimate}, actual={actual})')
|
||||||
|
|
||||||
|
if est_tokens_written >= args.target_tokens:
|
||||||
|
print(f'\n[done] target reached: {est_tokens_written/1e9:.2f}B est tokens')
|
||||||
|
break
|
||||||
|
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
print(f'\n{"=" * 60}')
|
||||||
|
print(f'Total: {n_written:,} samples, ~{est_tokens_written/1e9:.2f}B est tokens')
|
||||||
|
print(f'Elapsed: {elapsed/60:.1f} min ({elapsed/3600:.1f}h)')
|
||||||
|
print(f'Skipped (too short): {skipped_short:,}')
|
||||||
|
print(f'Output: {output_path} ({os.path.getsize(output_path)/1e9:.2f} GB)')
|
||||||
|
print(f'{"=" * 60}')
|
||||||
|
if tokenizer:
|
||||||
|
print(f'⚠️ 估算 token 数有 ~{abs(1-calibration_ratio)*100:.1f}% 误差。')
|
||||||
|
print(f' 精确数:跑 eval_perplexity.py 时会有 actual count。')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue