mamba-cot-tts / code /scripts /jsonl_to_parquet.py
maxpicy's picture
Add files using upload-large-folder tool
6e802a4 verified
Raw History Blame Contribute Delete
5.52 kB
# jsonl_to_parquet.py: Lossless corpus JSONL <-> parquet (to_row() rebuilds the exact training row).
import argparse
import hashlib
import json
import os
import sys
from multiprocessing import Pool
STR_COLS = ["prompt", "reasoning", "emotion_label", "pace", "pitch", "cot_form", "source", "speaker", "id", "file",
"caption_source"]
CHUNK = 20000
def _schema():
import pyarrow as pa
return pa.schema([(c, pa.string()) for c in STR_COLS] + [
("speaker_id", pa.int32()),
("speech_tokens", pa.list_(pa.int16())),
("residual_codes", pa.list_(pa.list_(pa.int16()))),
("plan", pa.string()),
("meta", pa.string()),
])
def _is_int(x):
return isinstance(x, int) and not isinstance(x, bool)
def from_row(r):
out = {c: None for c in STR_COLS + ["speaker_id", "speech_tokens", "residual_codes", "plan", "meta"]}
meta = {}
for k, v in r.items():
if k in STR_COLS and isinstance(v, str):
out[k] = v
elif k == "speaker_id" and _is_int(v) and -2 ** 31 <= v < 2 ** 31:
out[k] = v
elif k == "speech_tokens" and isinstance(v, list) and all(_is_int(x) and -32768 <= x < 32768 for x in v):
out[k] = v
elif (k == "residual_codes" and isinstance(v, list) and all(isinstance(lv, list) for lv in v)
and all(_is_int(x) and -32768 <= x < 32768 for lv in v for x in lv)):
out[k] = v
elif k == "plan" and isinstance(v, dict):
out[k] = json.dumps(v, sort_keys=True, separators=(",", ":"))
else:
meta[k] = v
out["meta"] = json.dumps(meta, sort_keys=True, separators=(",", ":")) if meta else None
return out
def to_row(c):
r = json.loads(c["meta"]) if c.get("meta") else {}
for k in STR_COLS + ["speaker_id", "speech_tokens", "residual_codes"]:
if c.get(k) is not None:
r[k] = c[k]
if c.get("plan") is not None:
r["plan"] = json.loads(c["plan"])
return r
def row_hash(r):
return hashlib.sha1(json.dumps(r, sort_keys=True, separators=(",", ":")).encode()).digest()
def _shard(task):
import pyarrow as pa
import pyarrow.parquet as pq
src, offset, n_rows, path = task
schema = _schema()
hashes, buf = [], []
tmp = path + ".tmp"
w = pq.ParquetWriter(tmp, schema, compression="zstd")
def flush():
cols = {name: [] for name in schema.names}
for r in buf:
for k, v in from_row(r).items():
cols[k].append(v)
w.write_table(pa.table(cols, schema=schema))
buf.clear()
with open(src, "rb") as f:
f.seek(offset)
for _ in range(n_rows):
line = f.readline()
if not line:
raise SystemExit(f"{src}: unexpected EOF in shard {path}")
r = json.loads(line)
hashes.append(row_hash(r))
buf.append(r)
if len(buf) >= CHUNK:
flush()
if buf:
flush()
w.close()
i = 0
for b in pq.ParquetFile(tmp).iter_batches(batch_size=CHUNK):
for c in b.to_pylist():
if i >= len(hashes) or row_hash(to_row(c)) != hashes[i]:
raise SystemExit(f"{path}: row {i} does not round-trip")
i += 1
if i != len(hashes):
raise SystemExit(f"{path}: {i} rows read back, {len(hashes)} written")
os.replace(tmp, path)
h = hashlib.sha256()
with open(path, "rb") as f:
for blk in iter(lambda: f.read(1 << 24), b""):
h.update(blk)
return {"file": os.path.basename(path), "rows": n_rows, "bytes": os.path.getsize(path), "sha256": h.hexdigest()}
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--src", required=True)
ap.add_argument("--out", required=True)
ap.add_argument("--split", default="train")
ap.add_argument("--shard_rows", type=int, default=100000)
ap.add_argument("--workers", type=int, default=8)
args = ap.parse_args()
os.makedirs(args.out, exist_ok=True)
offsets, n = [], 0
with open(args.src, "rb") as f:
pos = 0
for line in f:
if n % args.shard_rows == 0:
offsets.append(pos)
pos += len(line)
n += 1
k = len(offsets)
tasks = [(args.src, off, min(args.shard_rows, n - i * args.shard_rows),
os.path.join(args.out, f"{args.split}-{i:05d}-of-{k:05d}.parquet")) for i, off in enumerate(offsets)]
print(f"[parquet] {args.src}: {n} rows -> {k} shards of <= {args.shard_rows}", flush=True)
with Pool(args.workers) as pool:
res = []
for r in pool.imap(_shard, tasks):
print(f"[parquet] ok {r['file']}: {r['rows']} rows, {r['bytes'] / 1e9:.2f} GB, round-trip verified",
flush=True)
res.append(r)
assert sum(r["rows"] for r in res) == n
man = {"source": os.path.basename(args.src), "rows": n, "shards": res,
"source_bytes": os.path.getsize(args.src), "parquet_bytes": sum(r["bytes"] for r in res),
"lossless_round_trip_verified": True}
with open(os.path.join(args.out, f"{args.split}_manifest.json"), "w") as f:
json.dump(man, f, indent=1)
print(f"[parquet] DONE {args.src}: {n} rows, {man['source_bytes'] / 1e9:.2f} GB JSONL -> "
f"{man['parquet_bytes'] / 1e9:.2f} GB parquet", flush=True)
if __name__ == "__main__":
sys.exit(main())