阿塞拜疆语属于突厥语族,一个词通过后缀叠加可以表达英语整句话的含义——名词有 18 种格变,动词时态与人称通过层层黏着完成。这种"形态丰富"(morphologically rich)的语言对大模型来说意味着两件事:词表膨胀极快,训练数据却极其稀缺。Azercell 作为阿塞拜疆最大电信运营商,需要在六周内从零搭建一套生产级训练框架,把通用基础模型改造成能理解本地电信场景的阿塞拜疆语 LLM,用于内部业务和面向客户的聊天机器人。他们与 AWS Generative AI Innovation Center 合作,在 Amazon SageMaker AI 上完成了这条没有现成蓝图的路。
形态丰富语言的三重挤压
阿塞拜疆语不是"少几个词"的问题,而是结构性差异:
- 词表爆炸——英语
go加后缀产生goes / going / gone,阿塞拜疆语一个动词根可派生出几十种形态。如果照搬英语 LLM 的词表策略,token 覆盖率会急剧下降,大量词被拆成无意义子串。 - 数据稀缺——高质量阿塞拜疆语语料在公开数据集中占比极低。Common Crawl 里阿塞拜疆语页面不足 0.1%,且噪声严重。
- 无先例可循——英语、中文、德语都有成熟的预训练与微调路径,阿塞拜疆语几乎没有公开的基座模型或训练 recipe,每一步都需要验证。
Azercell 的做法是:不从头预训练,而是选取已有基础模型(Foundation Model),通过词表扩展 + 领域微调两条主线,把模型"嫁接"到阿塞拜疆语电信场景。
SageMaker AI 上的训练框架设计
六周时间要跑通从数据清洗到模型部署的全流程,关键在于把每一步都变成可复现的 pipeline,而不是手动 ad-hoc 操作。SageMaker AI 提供了几个核心能力来支撑这个框架:
- 托管训练集群:按需拉起 GPU 实例(如
ml.p4d.24xlarge),训练完自动释放,不用维护常驻集群。 - Processing Job:数据清洗、词频统计、词表构建等预处理步骤作为独立 job 运行,输入输出都走 S3,可审计可回溯。
- HyperParameter Tuning:形态丰富语言的微调超参没有文献参考,需要系统搜索。SageMaker 的 HPO 可以并行跑多组实验,自动收敛到较优配置。
- Model Registry + Endpoint:训练产出的模型直接注册,一键部署为推理端点,衔接聊天机器人场景。
整个流程被组织为:数据准备 → 词表扩展 → 继续预训练(CPT)→ 领域微调(SFT)→ 评估 → 部署,每一步对应一个或多个 SageMaker job,通过 Step Functions 或 Python SDK 串联。
实操:词表扩展与微调训练 Job
下面给出一个可以在 SageMaker AI 上直接运行的训练配置示例。场景是:对已有基座模型做词表扩展后,用阿塞拜疆语电信语料进行监督微调(SFT)。假设你已经把词表扩展后的模型权重上传到了 S3。
1. 准备训练脚本(train_sft.py)
将以下脚本打包为 sft_code.tar.gz,上传到 S3:
# train_sft.py — 阿塞拜疆语电信场景 SFT 训练入口
import argparse
import json
import os
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from trl import SFTTrainer
from datasets import load_dataset
def main():
parser = argparse.ArgumentParser()
# SageMaker 会把超参以 JSON 传入 /opt/ml/input/config/hyperparameters.json
parser.add_argument("--model_dir", type=str, default="/opt/ml/input/data/model")
parser.add_argument("--train_file", type=str, default="/opt/ml/input/data/train/sft_telecom_az.jsonl")
parser.add_argument("--epochs", type=int, default=3)
parser.add_argument("--lr", type=float, default=2e-5)
parser.add_argument("--batch_size", type=int, default=8)
parser.add_argument("--max_seq_length", type=int, default=2048)
parser.add_argument("--output_dir", type=str, default="/opt/ml/model")
args = parser.parse_args()
tokenizer = AutoTokenizer.from_pretrained(args.model_dir)
model = AutoModelForCausalLM.from_pretrained(
args.model_dir,
torch_dtype=torch.bfloat16,
device_map="auto",
)
# 加载阿塞拜疆语电信 SFT 数据(JSONL 格式:{"prompt":..., "response":...})
dataset = load_dataset("json", data_files=args.train_file, split="train")
def format_example(example):
return f"<s>[INST] {example['prompt']} [/INST] {example['response']}</s>"
training_args = TrainingArguments(
output_dir=args.output_dir,
num_train_epochs=args.epochs,
per_device_train_batch_size=args.batch_size,
learning_rate=args.lr,
bf16=True,
gradient_checkpointing=True,
logging_steps=50,
save_strategy="epoch",
remove_unused_columns=False,
)
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
args=training_args,
train_dataset=dataset,
formatting_func=format_example,
max_seq_length=args.max_seq_length,
)
trainer.train()
# SageMaker 要求最终模型写到 /opt/ml/model,会自动打包上传到 S3
trainer.save_model(args.output_dir)
tokenizer.save_pretrained(args.output_dir)
if __name__ == "__main__":
main()
2. 启动 SageMaker Training Job
import sagemaker
from sagemaker.pytorch import PyTorch
sagemaker_session = sagemaker.Session()
role = "arn:aws:iam::YOUR_ACCOUNT_ID:role/SageMakerExecutionRole"
# 训练代码包与数据在 S3 上的位置
code_uri = "s3://az-llm-bucket/code/sft_code.tar.gz"
model_uri = "s3://az-llm-bucket/models/az-llm-extended-vocab/" # 词表扩展后的基座模型
train_uri = "s3://az-llm-bucket/data/sft_telecom_az.jsonl" # 阿塞拜疆语电信 SFT 数据
estimator = PyTorch(
entry_point="train_sft.py",
source_dir=code_uri, # 也可以用本地目录,SDK 会自动上传
role=role,
instance_type="ml.p4d.24xlarge", # 8× A100 40GB,适合 7B-13B 模型微调
instance_count=1,
framework_version="2.1",
py_version="py310",
hyperparameters={
"epochs": 3,
"lr": 2e-5,
"batch_size": 8,
"max_seq_length": 2048,
},
# 把模型权重和数据作为 channel 传入,SageMaker 会下载到 /opt/ml/input/data/ 下对应子目录
channels={
"model": sagemaker.inputs.TrainingInput(s3_data=model_uri, input_mode="File"),
"train": sagemaker.inputs.TrainingInput(s3_data=train_uri, input_mode="File"),
},
output_path="s3://az-llm-bucket/output/",
sagemaker_session=sagemaker_session,
)
estimator.fit()
print(f"Training job: {estimator.latest_training_job.name}")
print(f"Model artifact: {estimator.model_data}")
运行前需要替换 YOUR_ACCOUNT_ID 和 S3 路径为你的实际值。ml.p4d.24xlarge 在部分区域可能需要申请容量配额;如果资源受限,可先用 ml.g5.12xlarge(4× A10G)做小规模验证。
3. SFT 数据格式参考
{"prompt": "Azercell的5G套餐有哪些选择?", "response": "Azercell目前提供三种5G套餐:Basic(每月29 AZN,含10GB流量)、Standard(49 AZN,含30GB流量+无限通话)、Premium(79 AZN,含无限流量+国际漫游100分钟。所有套餐均支持5G NSA接入,覆盖巴库主要城区。"}
{"prompt": "如何查询我的号码余额?", "response": "您可以通过以下方式查询余额:1. 发送短信BAL到7777;2. 登录Azercell自助服务App;3. 拨打*111#USSD代码。查询结果会显示主账户余额及当前套餐剩余流量。"}
数据质量是形态丰富语言微调的生命线——每条样本都要用母语人员审核,避免翻译腔和语法错误被模型学到。
词表扩展的取舍
词表扩展本身是一个容易被忽视但影响深远的步骤。常见做法是:
- 从阿塞拜疆语语料中统计高频子词(用 SentencePiece 或 HuggingFace tokenizer 的
train_new_from_iterator)。 - 将新 token 加入模型词表,对应的 embedding 行用已有 token 的均值初始化。
- 先用纯语言语料做继续预训练(CPT),让新 embedding 适应上下文,再进入 SFT。
风险在于:扩展比例过大会导致原有语言能力退化,扩展过小则阿塞拜疆语仍然被过度拆分。Azercell 的经验是控制在原词表 5%-10% 的增量,并在 CPT 阶段混入少量英语数据作为"锚点",防止模型在单语训练中遗忘通用能力。
部署与落地建议
训练完成后,模型通过 SageMaker Model Registry 注册,部署为实时推理端点:
from sagemaker.model_monitor import ModelRegistry
# 注册模型
model_package_group_name = "az-llm-telecom"
# ... 通过 estimator.model_data 创建 ModelPackage ...
# 部署为端点
predictor = estimator.deploy(
initial_instance_count=1,
instance_type="ml.g5.xlarge", # 推理用单卡 A10G 即可
endpoint_name="az-llm-telecom-endpoint",
)
聊天机器人服务调用该端点,将用户问题拼接为 <s>[INST] ... [/INST] 格式后发送请求,拿到模型生成的阿塞拜疆语回复。
落地检查清单
| 项目 | 要点 |
|---|---|
| 词表扩展比例 | 控制在 5%-10%,过大需回退测试 |
| CPT 数据配比 | 阿塞拜疆语为主,混入 5%-10% 英语作为能力锚点 |
| SFT 数据审核 | 必须由母语人员逐条检查,不接受机器翻译 |
| 评估指标 | 除通用 benchmark 外,增加电信场景专项测试集(套餐查询、故障报修、账单解释) |
| 推理延迟 | 阿塞拜疆语 token 数比英语多 20%-40%,需预留额外推理时间 |
| 成本控制 | 训练用 p4d 按需拉起,推理用 g5 持续部署;非高峰可考虑异步端点降本 |
形态丰富语言的 LLM 训练没有捷径,但 Azercell 的实践说明:在 SageMaker AI 上把数据准备、词表扩展、多阶段训练、HPO 搜索、模型部署都变成可复现的 pipeline,六周可以从零走到生产级。这套框架不只适用于阿塞拜疆语——任何面临"数据少、形态复杂、无先例"的语言,都可以按同样的骨架去搭建自己的训练路径。