1. 简介
代码助手,是一种基于 LLMs 的智能化的编程工具,它可以帮助程序员更高效、更准确的编写代码,使得整个软件开发过程更加流畅和高效。然而流行的代码助手,比如 Github Copilot,依赖于闭源的商业模型,不仅昂贵还会引起如隐私、安全、版权等方面的担忧。幸运的是,开源社区正在致力于打造开放代码模型来实现开放的代码助手。近期涌现出了一批优秀的 Open CodeLLMs,比如 StarCoder2、CodeLlama、DeepSeek-Coder 等,提供了一条新的路径,但仍然值得探索。
今天,来自 Qwen1.5 开源家族的新成员,代码专家模型 CodeQwen1.5开源!CodeQwen1.5 基于 Qwen 语言模型初始化,拥有 7B 参数的模型,其拥有 GQA 架构,经过了 ~3T tokens 代码相关的数据进行预训练,共计支持 92 种编程语言、且最长支持 64K 的上下文输入。效果方面,CodeQwen1.5 展现出了优秀的代码生成、长序列建模、代码修改、SQL 能力等,该模型可以大大提高开发人员的工作效率,并在不同的技术环境中简化软件开发工作流程。
CodeQwen 是基础的 Coder
代码生成是大语言模型的关键能力之一,期待模型将自然语言指令转换为具有精确的、可执行的代码。仅拥有 70 亿参数的 CodeQwen1.5 在基础代码生成能力上已经超过了更尺寸的模型,进一步缩小了开源代码 LLM 和 GPT-4 之间的编码能力差距。CodeQwen1.5 对 HumanEval 和 MBPP 进行了评估,以提供下面清晰的比较。
Model |
Size |
HumanEval 0-shot |
HumanEval+ 0-shot |
MBPP 0-shot |
MBPP+ 0-shot |
MBPP 3-shot |
Base Model |
||||||
CodeLlama-Base |
7B |
33.5 |
25.6 |
52.1 |
41.6 |
38.6 |
StarCoder2 |
7B |
35.4 |
29.9 |
54.4 |
45.6 |
51.0 |
DeepSeek-Coder-Base |
6.7B |
47.6 |
39.6 |
70.2 |
56.6 |
60.6 |
CodeQwen1.5 |
7B |
51.8 |
45.7 |
72.2 |
60.2 |
61.8 |
Chat Model |
||||||
GPT-3.5-Turbo |
- |
76.8 |
70.7 |
82.5 |
69.7 |
70.8 |
GPT-4-Turbo (Nov 2023) |
- |
85.4 |
81.7 |
83.5 |
70.7 |
80.0 |
DeepSeek-Coder-Instruct |
6.7B |
78.6 |
70.1 |
73.2 |
63.4 |
65.4 |
CodeQwen1.5-Chat |
7B |
83.5 |
78.7 |
77.7 |
67.2 |
70.6 |
除了流行的 Humaneval 与 MBPP 外,在 LiveCodeBench (2023-09-01->2024-04-01)上对 CodeQwen1.5 进行评估,结果展示出了 CodeQwen1.5 具竞争力的效果。但值得注意的是,在预训练语料中包含的 LeetCode 数据可能对该评测有帮助。
上述的评估主要围绕 Python 能力,但 CodeQwen1.5 不仅仅是 Python 专家,还是一个多编程语言专家。在 MultiPL-E 的 8 种主流语言(Python、C++、Java、PHP、TypeScript、C#、Bash,JavaScript)上对 CodeQwen1.5 进行全面评估。这些结果证明了 CodeQwen1.5 强大的编程能力。
1.1 CodeQwen 是长序列 Coder
长序列能力对于代码模型来说至关重要,是理解仓库级别代码、成为 Code Agent 的核心能力。而当前的代码模型对于长度的支持仍然非常有限,阻碍了其实际应用的潜力。CodeQwen1.5 希望进一步推进开源代码模型在长序列建模上的进展,收集并构造了仓库级别的长序列代码数据进行预训练,通过精细的数据配比和组织方式,使其最终可以最长支持 64K 的输入长度。
评估一:选择了不在 CodeQwen1.5 训练数据、最新产生的高质量 github 仓库 (来自 2024-3-28 的 Github Trending 仓库),来观测其长序列建模的有效性。下图可以发现在序列不断增长的情况下, CodeQwen1.5 的 PPL 仍然可以保持下降的趋势。
评估二:一个名为 Needle in the Code 的合成任务, 其效仿文本领域流行的长序列评测。在一个较长的代码库(CodeQwen选择了 Megatron,向其对开源 LLMs 的贡献致敬)的不同位置中插入非常简单的一个自定义函数,测试模型能否在代码库最后重复这个函数。下图可以发现,CodeQwen 能够在 64k 长度范围内仍然可以很好的完成这个任务。
无论是评估一还是评估二,都是初步的、基础的评估方式,仅仅是一个起点而非全部。但是,对于 Chat 模型,希望用更实际的任务来评估其长序列能力。
评估三:SWE Bench 的目的是解决真实软件开发中的问题,给定一个代码仓库和 issue,期待 LLMs/Agents 能够给出相应的 commit patch 来解决这个 issue。SWE Bench 对 Code LLMs 的长序列能力提出了更高的要求,不仅需要理解代码仓库,还要生成可通过单测的代码。
目前 SWEBench 竞技场上的玩家都依赖闭源模型, CodeQwen1.5 首次入局,尽管仅有 0.89 的分数但仍强于 ChatGPT3.5,这展示了开源代码模型与专有模型的竞争力尽管尚处于初期,但具有潜力。
1.2 CodeQwen 是优秀的代码修改者
一个好的代码助手不仅可以根据指令生成代码,还能够针对已有代码或者新的需求进行修改或错误修复。为此评估了 CodeQwen1.5 在代码修改方面的能力。首先在关注 CodeEditorBench,涉及到 Debug、Translate、Switch、Polish 等四个方面的代码修改能力,结果表明 CodeQwen1.5 在 7B 规模上达到了比较好的效果。
1.3 CodeQwen 是出色的 SQL 专家
CodeQwen1.5 可以作为一个智能的 SQL 专家,弥合了非编程专业人士与高效数据交互之间的差距。它通过自然语言使无编程专业知识的用户能够查询数据库,从而缓解了与SQL相关的陡峭学习曲线。在两个流行的文本到SQL基准测试Spider和Bird上评估了CodeQwen1.5-Chat的性能。实验结果显示,CodeQwen1.5在接近GPT-4的位置排名第二(结果来自DIN-SQL,一种 SOTA 的提示方法)。这一出色的表现得益于在预训练和微调阶段均广泛利用了合成数据。合成数据具有可扩展性、可验证性和多样性的特点,在增强CodeQwen1.5的SQL能力方面已被证明是一项具有吸引力的未来研究领域。
模型体验
模型测试效果很不错:
2. 模型链接和下载
CodeQwen模型系列现已在ModelScope社区开源,包括:
社区支持通过git或者SDK直接下载模型的repo:
from modelscope import snapshot_download model_dir = snapshot_download("qwen/CodeQwen1.5-7B-Chat")
3. 模型推理
3.1 环境配置和安装:
- python 3.10及以上版本
- pytorch推荐2.0及以上版本
- 建议使用CUDA 11.8及以上
本文在魔搭社区免费提供的GPU免费算力上体验:
模型推理
from modelscope import AutoModelForCausalLM, AutoTokenizer device = "cuda" # the device to load the model onto model = AutoModelForCausalLM.from_pretrained( "qwen/CodeQwen1.5-7B-Chat", torch_dtype="auto", device_map="auto" ) tokenizer = AutoTokenizer.from_pretrained("qwen/CodeQwen1.5-7B-Chat") prompt = "Write a quicksort algorithm in python." messages = [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt} ] text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) model_inputs = tokenizer([text], return_tensors="pt").to(device) generated_ids = model.generate( model_inputs.input_ids, max_new_tokens=512 ) generated_ids = [ output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids) ] response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0] print(response)
显存占用:
可见,GQA的架构,对于显存占有有一定的优化效果。
模型微调和微调后推理
我们使用swift来对模型进行微调, swift是魔搭社区官方提供的LLM&AIGC模型微调推理框架.
微调代码开源地址
我们使用leetcode-python-en数据集进行微调. 任务是: 解python程序题
环境准备:
git clone https://github.com/modelscope/swift.git cd swift pip install .[llm]
微调脚本: LoRA
# https://github.com/modelscope/swift/blob/main/examples/pytorch/llm/scripts/codeqwen1half_7b_chat/lora/sft.sh # Experimental environment: A100 # 25GB GPU memory CUDA_VISIBLE_DEVICES=0 \ swift sft \ --model_type codeqwen1half-7b-chat \ --model_revision master \ --sft_type lora \ --tuner_backend peft \ --dtype AUTO \ --output_dir output \ --ddp_backend nccl \ --dataset leetcode-python-en \ --train_dataset_sample -1 \ --num_train_epochs 3 \ --max_length 2048 \ --check_dataset_strategy warning \ --lora_rank 8 \ --lora_alpha 32 \ --lora_dropout_p 0.05 \ --lora_target_modules DEFAULT \ --gradient_checkpointing true \ --batch_size 1 \ --weight_decay 0.1 \ --learning_rate 1e-4 \ --gradient_accumulation_steps 16 \ --max_grad_norm 0.5 \ --warmup_ratio 0.03 \ --eval_steps 100 \ --save_steps 100 \ --save_total_limit 2 \ --logging_steps 10 \
训练过程也支持本地数据集,需要指定如下参数:
--custom_train_dataset_path xxx.jsonl \ --custom_val_dataset_path yyy.jsonl \
自定义数据集的格式可以参考
微调后推理脚本: (这里的ckpt_dir需要修改为训练生成的checkpoint文件夹)
# Experimental environment: 3090 CUDA_VISIBLE_DEVICES=0 \ swift infer \ --ckpt_dir "output/codeqwen1half-7b-chat/vx-xxx/checkpoint-xxx" \ --load_dataset_config true \ --max_new_tokens 2048 \ --temperature 0.1 \ --top_p 0.7 \ --repetition_penalty 1. \ --do_sample true \ --merge_lora false \
3.2 微调的可视化结果
训练loss:
训练后生成样例:
[PROMPT]<|im_start|>system You are a helpful assistant.<|im_end|> <|im_start|>user Given an `m x n` integer matrix `matrix`, if an element is `0`, set its entire row and column to `0`'s. You must do it [in place](https://en.wikipedia.org/wiki/In-place_algorithm). **Example 1:** **Input:** matrix = \[\[1,1,1\],\[1,0,1\],\[1,1,1\]\] **Output:** \[\[1,0,1\],\[0,0,0\],\[1,0,1\]\] **Example 2:** **Input:** matrix = \[\[0,1,2,0\],\[3,4,5,2\],\[1,3,1,5\]\] **Output:** \[\[0,0,0,0\],\[0,4,5,0\],\[0,3,1,0\]\] **Constraints:** * `m == matrix.length` * `n == matrix[0].length` * `1 <= m, n <= 200` * `-231 <= matrix[i][j] <= 231 - 1` **Follow up:** * A straightforward solution using `O(mn)` space is probably a bad idea. * A simple improvement uses `O(m + n)` space, but still not the best solution. * Could you devise a constant space solution? <|im_end|> <|im_start|>assistant [OUTPUT]```python def setZeroes(matrix): m, n = len(matrix), len(matrix[0]) rows, cols = set(), set() for i in range(m): for j in range(n): if matrix[i][j] == 0: rows.add(i) cols.add(j) for i in rows: for j in range(n): matrix[i][j] = 0 for j in cols: for i in range(m): matrix[i][j] = 0 ``` The algorithm first iterates through the matrix to find the rows and columns that need to be set to 0. It uses two sets, `rows` and `cols`, to store the indices of the rows and columns that need to be set to 0. After finding the rows and columns, the algorithm iterates through the rows and sets all the elements in that row to 0. Then, it iterates through the columns and sets all the elements in that column to 0. This algorithm has a time complexity of O(m * n) and a space complexity of O(m + n), which is the best possible for this problem.<|im_end|> [LABELS]```python def setZeroes(matrix): rows = len(matrix) cols = len(matrix[0]) firstRow, firstCol = False, False for i in range(rows): for j in range(cols): if matrix[i][j] == 0: if i == 0: firstRow = True if j == 0: firstCol = True matrix[i][0] = 0 matrix[0][j] = 0 for i in range(1, rows): for j in range(1, cols): if matrix[i][0] == 0 or matrix[0][j] == 0: matrix[i][j] = 0 if firstRow: for j in range(cols): matrix[0][j] = 0 if firstCol: for i in range(rows): matrix[i][0] = 0 ``` 1. Iterate over the matrix, marking the first element of the row and column for every 0 encountered. 2. Set a flag if the first row or the first column has a 0. 3. Iterate over the matrix, ignoring the first row and first column. Set the element to 0 if the corresponding first row or column is marked. 4. Finally, set the first row and column to 0 if their flags are set. -------------------------------------------------- [PROMPT]<|im_start|>system You are a helpful assistant.<|im_end|> <|im_start|>user Given an array `nums` of size `n`, return _the majority element_. The majority element is the element that appears more than `⌊n / 2⌋` times. You may assume that the majority element always exists in the array. **Example 1:** **Input:** nums = \[3,2,3\] **Output:** 3 **Example 2:** **Input:** nums = \[2,2,1,1,1,2,2\] **Output:** 2 **Constraints:** * `n == nums.length` * `1 <= n <= 5 * 104` * `-109 <= nums[i] <= 109` **Follow-up:** Could you solve the problem in linear time and in `O(1)` space? <|im_end|> <|im_start|>assistant [OUTPUT]```python def majorityElement(nums): count = 0 candidate = None for num in nums: if count == 0: candidate = num count += (1 if num == candidate else -1) return candidate ``` The algorithm uses a modified version of the Boyer-Moore Voting Algorithm. It initializes a counter `count` to 0 and a candidate variable to store the potential majority element. Then, it iterates through the input array `nums`. If the counter is 0, it updates the candidate to the current element. If the current element is equal to the candidate, it increments the counter; otherwise, it decrements the counter. After the iteration, the candidate variable will hold the majority element. This algorithm has a linear time complexity of O(n) and constant space complexity of O(1), making it efficient for the follow-up question.<|im_end|> [LABELS]```python def majority_element(nums): count = 0 candidate = None for num in nums: if count == 0: candidate = num count += (num == candidate) - (num != candidate) return candidate ``` The algorithm used here is called the Boyer-Moore Majority Vote algorithm. It works by initializing a candidate element and a count. Then, we iterate through the given array. If the count is 0, we set the candidate to the current element in the array. Then we update the count by adding 1 if the current element is equal to the candidate, or subtracting 1 otherwise. At the end of the iteration, the candidate will have the majority element. The algorithm works because if there is a majority element, it will always appear more than the sum of all other elements, so the count will not be 0 at the end.
资源消耗
4. 结论
CodeQwen1.5-7B 及 CodeQwen1.5-7B-Chat,一个开放的、多面体的 Code LLM,希望这个模型能在 Code 助手、Code Agent 等方面为社区贡献。未来通义千问团队仍然会积极投入代码智能建设,实现真正的 AI 程序员。