项目简介
随着互联网+的生态模式和人工智能的产业化发展,程序设计已成为计算机专业乃至工科学生的必备技能之一。学生学习程序设计,不仅能提高代码水平能力,学会如何写代码,如何写好代码,而且能锻炼学生在今后面对项目开发等实际应用场景时解决问题的能力。因此,很多同学在刚刚接触到编程时往往需要练习一些简单的程序设计题目。如大一时的高级程序设计、C++程序设计等课程,老师在讲课之余往往会布置一些程序设计的习题,让同学自己去练习,在亲身实践中巩固知识,增进对编程的理解。但软件学院至今没有自己的在线评测系统,老师布置习题时往往只能选用PTA等现有平台,PTA等平台手动添加题目不便,题目往往都是经典案例,很容易就能在网络上找到相关解法,不利于同学们独立思考、独立解题。同时教师在授课的过程中往往无法根据教学进度和学生们的实际掌握程度添加适合学生练习的题目。如果学院有一个自行研发的在线评测系统,有助于教师更灵活自由地开展各种教学活动,如使用该平台可以进行上机实操的期中考试。
然而,与其他传统学科不同,程序设计可能出现的错误千变万化,有时十分隐晦。这也使得其入门门槛较高,且常常需要耗费讲师和助教大量的时间和精力帮助初学者 debug。在这样的问题背景下,我们想要获得一个AI助手,让它帮助学生进行代码纠错,从而方便了教师教学的同时提高了学生的学习效率。微软推出的 Copilot 是一款通过理解用户输入的代码注释或部分代码片段、自动生成或补全代码的AI助手。它的问世说明了大模型具备相当不错的修改和续写代码的能力。我们希望可以通过微调,得到一个更加高效的、主要面向初学者的代码纠错工具。为了生成更具教育意义的代码,我们决定使用数百道经典例题和他们相应解法的 规范代码 对 CodeGeeX2-6B 进行微调。
架构设计: 设计一个分布式系统,包括前端、后端和纠错大模型组件。
前端: 开发一个基本需求功能齐全同时对用户友好的网页界面,主要包括用户提交代码、查看纠错结果以及提供反馈信息等功能。前端会将用户的代码发送给后端,等待后端程序的处理并接受后端发送过来的纠错信息和反馈信息,并将信息以合适的方式显示在前端告知用户。
后端: 构建一个高性能的服务器端应用程序,负责接收用户提交的代码,并对代码进行评测,若评测通过,程序将会返回给前端代码通过(AC)信息。若评测无法通过,则将其发送给纠错大模型进行处理,并接受纠错信息和反馈信息,一并将这些信息发送给前端。
纠错大模型: 选择和集成一个强大的代码纠错大模型,用于自动分析和纠正用户提交的代码中的错误。这里的大模型我们选择使用清华大学开源的ChatGLM-CodeGeeX2(https://github.com/THUDM/ChatGLM-6B)。 在该模型的基础上,选用程序设计试题的专门数据,进行Fine-turning的训练(即微调)
UOJ平台的部署与学习
参照网络上UOJ平台部署的教程,进行我们自己UOJ评测平台的部署。
首先,在Linux系统下安装docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo systemctl start docker # 启动 docker 服务
sudo docker --version # 检查 docker 是否正常安装
执行下面的指令来开始安装 UOJ:
sudo docker pull universaloj/uoj-system
sudo docker run --name uoj -dit -p 80:80 --cap-add SYS_PTRACE universaloj/uoj-system
接下来,UOJ将在后台运行。
稍等几分钟,访问服务器的地址,能看到UOJ正在运行了。
注册的第一个用户将被自动设为管理员。
docker的学习笔记
Docker 相当于一台虚拟机。
我们把虚拟机称作“容器(container)”,容器需要基于“镜像(image)”来运行。
容器的维护需要以下命令来实现
ocker start uoj # 启动 uoj 容器
docker stop uoj # 暂停 uoj 容器
docker restart uoj # 重启 uoj 容器
docker exec -it uoj /bin/bash # 进入 uoj 容器的终端
docker commit uoj uoj_back:tag # 将 uoj 容器保存为标签为 tag 的 uoj_back 镜像
docker ps -a # 查看所有的容器
docker images # 查看所有的镜像
docker rm uoj # 删除 uoj 容器(慎用)
docker rmi uoj_back:tag # 删除标签为 tag 的 uoj_back 镜像,
在docker中,每个容器需要基于一个镜像来运行。最开始,我们的uoj容器是基于安装过程中构建的镜像。
下面是一次典型的数据恢复:
docker commit uoj uoj_back:20170101 # 例行备份
docker commit uoj uoj_back:20170201 # 例行备份
docker commit uoj uoj_back:20170301 # 例行备份
# 3月15日,黑恶势力破坏了 uoj,uoj 容器已经无用,必须恢复
docker stop uoj
docker rm uoj # 删除uoj容器
docker run --name uoj -dit -p 80:80 --cap-add SYS_PTRACE uoj_back:20170301
# 创建新的 uoj 容器,使用3月1日的备份
数据迁移:
下面的命令可以把 uoj_back 这个容器导出为 uoj.tar 这个文件:
docker save -o uoj.tar uoj_back
把 uoj.tar 复制到其他机器上,然后可以运行下面的命令导入 uoj_back 镜像:
docker load --input uoj.tar
下面是一次典型的数据迁移:
# 服务器 A:
docker commit uoj uoj_back # 把uoj容器存储为镜像
docker save -o uoj.tar uoj_back # 把uoj_back镜像导出
# 然后通过某些恶毒的手段把 uoj.tar 拷到服务器 B
# 服务器 B:
docker load --input uoj.tar # 导入uoj_back镜像
docker run --name uoj -dit -p 80:80 --cap-add SYS_PTRACE uoj_back
# 建立 uoj 容器,基于之前服务器 A 的数据,迁移完成`
系统管理
1.管理员账户
UOJ 安装完成后,注册的第一个账户会被自动设为管理员账户。
当以管理员账户登录 UOJ 后,可以依次点击页面右上角您的用户名、系统管理来进入管理界面。
管理员账户有发布公告、新建题目和比赛等权限。但是,管理员并不能参加比赛。
2.发布公告
UOJ 发布公告的套路比较神奇。
首先,将公告发布到博客当中,然后再这篇博客的地址中(形如.../blog/478)找到最后的一串数字,记作博客 id。
之后,打开管理界面,使用博客 id 添加公告。
可以置顶公告,需要注意的是,1 表示优先级最低(显示为“三级置顶”),3 表示优先级最高(显示为“一级置顶”)。
3.全局配置文件
全局配置文件中包含了网站的基础信息。要修改全局配置文件,请先进入 Docker 容器的终端。
sudo docker exec -it uoj /bin/bash
全局配置文件的路径是/var/www/uoj/app/.config.php
,您可能需要用 Vim 编辑该文件。
注意,该文件是隐藏文件,您可能需要通过ls -a命令才能看到它。
您可以再全局配置文档中修改网站的标题等信息,也可以修改发送邮件的配置。发送邮件的邮箱强烈建议您使用 sina 邮箱。
这个文件大概是这个样子的:
点击查看代码
<?php
return array (
'profile' => // 网站相关信息
array (
'oj-name' => 'Universal Online Judge', // 网站全称
'oj-name-short' => 'UOJ', // 网站简称
'administrator' => 'root', // 管理员 id(仅在关于中显示)
'admin-email' => 'admin@local_uoj.ac', // 管理员邮箱(仅在关于中显示)
'QQ-group' => '', // OJ QQ 交流群(仅在关于中显示)
'ICP-license' => '', // ICP 备案信息
),
'database' => // 数据库相关(勿动)
array (
'database' => 'app_uoj233', // 数据库名称
'username' => 'root', // 用户名
'password' => 'root', // 密码
'host' => '127.0.0.1', // 数据库主机名
),
'web' => // 网址相关(勿动)
array (
'domain' => NULL,
'main' =>
array (
'protocol' => 'http',
'host' => isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ''),
'port' => 80,
),
'blog' =>
array (
'protocol' => 'http',
'host' => isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ''),
'port' => 80,
),
),
'security' => // 安全相关(勿动),仅为示例
array (
'user' =>
array (
'client_salt' => 'salt',
),
'cookie' =>
array (
'checksum_salt' =>
array (
0 => 'salt',
1 => 'salt',
2 => 'salt',
),
),
),
'mail' => // 邮件相关(SMTP 协议发送)
array (
'noreply' => // noreply 邮箱(建议使用 sina 邮箱)
array (
'username' => 'noreply@none',
'password' => 'noreply',
),
),
'judger' => // 测评相关(勿动)
array (
'socket' => // 与测评机的 socket 服务器通讯的设置
array (
'port' => 2333, // 端口
'password' => 'pass', // 认证密码(证明自己 UOJ 服务器),仅为示例
),
),
'svn' => // svn 相关(勿动)
array (
'our-root' => // 每个题目的 svn 仓库自带的仓库管理员
array (
'username' => 'our-root', // 管理员用户名
'password' => 'pass', // 密码,仅为示例
),
),
'switch' => // 一些开关
array (
'ICP-license' => false, // 是否显示 ICP 备案信息(默认为否)
'web-analytics' => false, // 是否进行网站统计(默认为否,记 uoj.ac 名下,想统计自己的得改代码)
'blog-use-subdomain' => false, // 博客是否使用子域名(默认为否,不建议使用)
),
);
4.LOGO
LOGO 图片位于网站的/picture目录下,使用 SVN 管理。
首先,需要 checkout UOJ 的 web 目录:
svn checkout svn://<local-uoj-address>/uoj
cd uoj/1/pictures
在 /pictures 目录下替换对应的图片,然后 commit。
svn commit -m "Changed logo" *
多评测机配置
在原 UOJ 容器所在物理机上安装
使用自动构建的镜像(推荐)
首先,请进入 UOJ 容器,并记录 /opt/uoj/judger/.conf.json
的内容。
执行下面的命令,从 DockerHub 中获取评测机镜像:
sudo docker pull universaloj/uoj-system:judger`
请新建文件 judger_env.list
,请根据下文说明替换下面的文件内容:
UOJ_PROTOCOL=http
UOJ_HOST=<uoj_host>
JUDGER_NAME=<judger_name>
JUDGER_PASSWORD=<judger_pwd>
SOCKET_PORT=2333
SOCKET_PASSWORD=<socket_pwd>
您需要替换:
- <judger_name>:新评测机的名称,不应与之前的评测机重复,仅可由小写字母和数字组成。
- <uoj_host>:UOJ 容器的容器 IP。
- 可通过 docker inspect --format '{{ .NetworkSettings.IPAddress }}' <container_id> 来获得名为 <container_id> 的容器的 IP
- <judger_pwd>:评测机密码,可以不修改。
- <socket_pwd>:通讯密码,应与您在第一步中从 UOJ 容器中获得的密码相同。
- 接下来,在 judger_env.list 所在目录下,执行下面的命令来启动评测机。其中,请将 <judger_name> 替换为评测机的名称。
sudo docker run --name <judger_name> -dit --env-file judger_env.list --cap-add SYS_PTRACE universaloj/uoj-system:judger
启动后,请您查看新启动的评测容器的 IP 并记录下来。之后,您应该进入 UOJ 容器中的 mysql 中的 app_uoj233 数据库, 并将新的评测机的信息用以下命令加入数据库(请不要在网页端后台操作添加):
insert into judger_info (judger_name, password, ip) values ('<judger_name>', '<judger_pwd>', '<judger_ip>');
最后,退出 mysql 和 docker。
此时,如果配置正确,您应该可以在网页端看到新的评测机了。您可以通过同时提交数个死循环程序,来检查多个评测机是否工作正常。
注意:对于每道题,您需要在新评测机配置完成后点击题目管理页面中的“检查配置并同步数据”来将数据同步到每台评测机。
使用 Dockerfile 手动构建
首先,您需要查看 UOJ 容器的 judger 配置并按照其配置配置新的评测机。请先在终端中进入一个临时文件夹,用于存放临时文件。之后,您需要从 UOJ 容器中复制出/opt/uoj/judger/.conf.json:
docker cp uoj:/opt/uoj/judger/.conf.json ./conf.json
然后,请您编辑复制出的 conf.json
,修改:
- uoj_host:UOJ 容器的容器IP(某容器的IP可通过命令docker inspect --format '{{ .NetworkSettings.IPAddress }}'
来获取) 。 - jugder_name:新评测机的名称,不应与之前的评测机重复,仅可由小写字母和数字组成。
- judger_password:密码,可以不修改。
下一步,您需要通过 Dockerfile 构建新 judger 的镜像。
新建一个名为 Dockerfile
的文件,其内容如下:
FROM ubuntu:18.04
MAINTAINER MascoSkray <[email protected]>
ARG CLONE_ADDFLAG
WORKDIR /opt
#Update apt and install git
RUN apt-get update && apt-get install -y git
#Clone the latest UOJ Community verison to local
RUN git clone https://github.com/UniversalOJ/UOJ-System.git --depth 1 --single-branch ${CLONE_ADDFLAG} uoj
#Install environment and set startup script
COPY conf.json /opt/uoj/judger/.conf.json
RUN cd uoj/install/judger && sh install.sh -p && echo "\
#!/bin/sh\n\
if [ ! -f \"/opt/uoj/judger/.conf.json\" ]; then\n\
cd /opt/uoj/install/judger && sh install.sh -i\n\
fi\n\
service ntp start\n\
su judger -c \"/opt/uoj/judger/judge_client start\"\n\
exec bash\n" >/opt/up && chmod +x /opt/up
ENV LANG=C.UTF-8 TZ=Asia/Shanghai
EXPOSE 2333
CMD /opt/up
该 Dockerfile 相对于原始文件,仅增加了将我们修改过的 conf.json 复制到容器的相应位置的代码。在某些国家,由于某些限制,可能导致 clone 过程执行过慢,您可以考虑将仓库手动克隆到您所在国家的代码托管网站。
使用如下命令构建该镜像:
docker build -f Dockerfile .
在经历漫长的等待后,如果构建过程正常,您将可以看到:
Successfully built <image-id>
请记录下 <image-id>
。
然后,您需要使用刚才构建的镜像启动一个新的评测容器:
docker run -dit --name <judger-name> --cap-add SYS_PTRACE <image-id>
启动后,请您查看新启动的评测容器的 IP 并记录下来。之后,您应该进入 UOJ 容器中的 mysql 中的 app_uoj233, 并将新的评测机的信息加入数据库(请不要在网页端后台操作添加):
insert into judger_info (judger_name, password, ip) values ('<judger_name>', '<judger_password>', '<judger_ip>');
退出 mysql ,按组合键 Ctrl+P+Q
离开并不停止容器。
此时,如果配置正确,您应该可以在网页端看到新的评测机了。
注意:对于每道题,您需要在新评测机配置完成后点击题目管理页面中的“检查配置并同步数据”来将数据同步到每台评测机。
如果需要安装更多评测机,不需要重新构建镜像。请直接使用刚才的命令从之前的镜像启动一个新的 judger 容器。然后,进入这个新的 judger 容器,并编辑
/opt/uoj/judger/.conf.json
为新 judger 的信息。之后,保存文件、退出容器并重启该容器。然后按照第四步的方式将新的评测机加入数据库。
本地构建镜像前的准备
准备一个装有 64 位 Linux 系统的服务器,这里建议使用 Ubuntu。在进行构建之前,建议先更新系统软件包,并安装必要的工具。
sudo apt update
sudo apt upgrade
sudo apt install git curl
接下来需要安装 docker,它是一种容器技术,暂且可以大致地理解成虚拟机。
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo systemctl enable --now docker # 启用并启动 docker 服务
sudo docker --version # 检查 docker 是否正常安装
克隆 UOJ 源码
执行下面的命令,从 GitHub 克隆 UOJ 源码。
git clone https://github.com/UniversalOJ/UOJ-System.git
使用 Docker 构建 UOJ
执行下面的命令,从源码构建 UOJ 镜像。
cd UOJ-System/install/bundle/
sudo docker build -t uoj-system .
可能需要等待一段时间才能完成构建。构建结束时,屏幕上会显示:
Successfully built xxxxxx
Successfully tagged uoj-system:latest
构建结束后,下列命令可以使用本地构建的镜像运行 UOJ 。
sudo docker run --name uoj -dit -p 80:80 --cap-add SYS_PTRACE uoj-system
稍等几分钟,访问服务器的地址,应该就能看到 UOJ 的默认主页了构建镜像