From dc6859f1e068f2c356f38e7d44cf2a005a49020c Mon Sep 17 00:00:00 2001 From: zhouti Date: Mon, 7 Nov 2016 15:33:00 +0800 Subject: [PATCH 001/119] Added paddle on kubernetes tutorial. --- doc/kubernetes_on_paddle.md | 200 ++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 doc/kubernetes_on_paddle.md diff --git a/doc/kubernetes_on_paddle.md b/doc/kubernetes_on_paddle.md new file mode 100644 index 0000000000..2f3109e87b --- /dev/null +++ b/doc/kubernetes_on_paddle.md @@ -0,0 +1,200 @@ +# Paddle On Kubernetes + +>In this article, we will introduce how to run Paddle training job on single CPU machine using Kubernetes. In next article, we will introduce how to run Paddle training job on distributed cluster. + +## Build Docker Image + +In distributed Kubernetes cluster, we will use Ceph or other shared storage system for storing training related data so that all processes in Paddle training can retrieve data from Ceph. In this example, we will only demo training job on single machine. In order to simplify the requirement of the environment, we will directly put training data into Paddle's Docker Image, so we need to create a Paddle Docker image that already includes the training data. + +Paddle's [Quick Start Tutorial](http://www.paddlepaddle.org/doc/demo/quick_start/index_en.html) introduces how to download and train data by using script from Paddle's source code. +And `paddledev/paddle:cpu-demo-latest` image has the Paddle source code and demo. (Caution: Default Paddle image `paddledev/paddle:cpu-latest` doesn't include the source code, Paddle's different versions of image can be referred here: [Docker installation guide](http://www.paddlepaddle.org/doc/build/docker_install.html)), so we run this container and download the training data, and then commit the whole container to be a new Docker image. + +### Run Docker Container + +``` +$ docker run --name quick_start_data -it paddledev/paddle:cpu-demo-latest +``` + +### Download Training Data + +Getting into `/root/paddle/demo/quick_start/data` Directory,using `get_data.sh` to download training data. + +``` +$ root@fbd1f2bb71f4:~/paddle/demo/quick_start/data# ./get_data.sh + +Downloading Amazon Electronics reviews data... +--2016-10-31 01:33:43-- http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/reviews_Electronics_5.json.gz +Resolving snap.stanford.edu (snap.stanford.edu)... 171.64.75.80 +Connecting to snap.stanford.edu (snap.stanford.edu)|171.64.75.80|:80... connected. +HTTP request sent, awaiting response... 200 OK +Length: 495854086 (473M) [application/x-gzip] +Saving to: 'reviews_Electronics_5.json.gz' + + 10% [=======> ] 874,279 64.7KB/s eta 2h 13m + +``` + +### Modify Startup Script + +After downloading the data,modify `/root/paddle/demo/quick_start/train.sh` file contents are as follows (one more cd cmd): +``` +set -e +cd /root/paddle/demo/quick_start +cfg=trainer_config.lr.py +#cfg=trainer_config.emb.py +#cfg=trainer_config.cnn.py +#cfg=trainer_config.lstm.py +#cfg=trainer_config.bidi-lstm.py +#cfg=trainer_config.db-lstm.py +paddle train \ + --config=$cfg \ + --save_dir=./output \ + --trainer_count=4 \ + --log_period=20 \ + --num_passes=15 \ + --use_gpu=false \ + --show_parameter_stats_period=100 \ + --test_all_data_in_one_period=1 \ + 2>&1 | tee 'train.log' +``` + +### Commit Docker Image + +``` +$ docker commit quick_start_data mypaddle/paddle:quickstart +``` + +## Use Kubernetes For Training + +>We will use Kubernetes job for training process, following steps shows how to do the training with Kubernetes. + +### Create Yaml Files + +The output result in container will be demolished when job finished (container stopped running), so we need to mount the volume out to the local disk when creating the container to store the training result. Using our previously created image, we can create a [Kubernetes Job](http://kubernetes.io/docs/user-guide/jobs/#what-is-a-job), the yaml contents are as follows: + +``` +apiVersion: batch/v1 +kind: Job +metadata: + name: quickstart +spec: + parallelism: 1 + completions: 1 + template: + metadata: + name: quickstart + spec: + volumes: + - name: output + hostPath: + path: /home/work/paddle_output + containers: + - name: pi + image: mypaddle/paddle:quickstart + command: ["bin/bash", "-c", "/root/paddle/demo/quick_start/train.sh"] + volumeMounts: + - name: output + mountPath: /root/paddle/demo/quick_start/output + restartPolicy: Never +``` + +### Start Paddle Job + +Using the above yaml file to start the Kubernetes job. + +``` +$ kubectl create -f paddle.yaml +``` + +Get the detailed status of the job: + +``` +$ kubectl get job +NAME DESIRED SUCCESSFUL AGE +quickstart 1 0 58s + +$ kubectl describe job quickstart +Name: quickstart +Namespace: default +Image(s): registry.baidu.com/public/paddle:cpu-demo-latest +Selector: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84 +Parallelism: 1 +Completions: 1 +Start Time: Mon, 31 Oct 2016 11:20:16 +0800 +Labels: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84,job-name=quickstart +Pods Statuses: 0 Running / 1 Succeeded / 0 Failed +Volumes: + output: + Type: HostPath (bare host directory volume) + Path: /home/work/paddle_output +Events: + FirstSeen LastSeen Count From SubobjectPath Type Reason Message + --------- -------- ----- ---- ------------- -------- ------ ------- + 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: quickstart-fa0wx +``` + +### Get Training Result + +We can use kubectl command to take a look at the status of related pod. + +``` +$ kubectl describe pod quickstart-fa0wx +Name: quickstart-fa0wx +Namespace: default +Node: paddle-demo-let02/10.206.202.44 +Start Time: Mon, 31 Oct 2016 11:20:17 +0800 +Labels: controller-uid=f120da72-9f18-11e6-b363-448a5b355b84,job-name=quickstart +Status: Succeeded +IP: 10.0.0.9 +Controllers: Job/quickstart +Containers: + quickstart: + Container ID: docker://b8561f5c79193550d64fa47418a9e67ebdd71546186e840f88de5026b8097465 + Image: registry.baidu.com/public/paddle:cpu-demo-latest + Image ID: docker://18e457ce3d362ff5f3febf8e7f85ffec852f70f3b629add10aed84f930a68750 + Port: + Command: + bin/bash + -c + /root/paddle/demo/quick_start/train.sh + QoS Tier: + cpu: BestEffort + memory: BestEffort + State: Terminated + Reason: Completed + Exit Code: 0 + Started: Mon, 31 Oct 2016 11:20:20 +0800 + Finished: Mon, 31 Oct 2016 11:21:46 +0800 + Ready: False + Restart Count: 0 + Environment Variables: +Conditions: + Type Status + Ready False +Volumes: + output: + Type: HostPath (bare host directory volume) + Path: /home/work/paddle_output +``` + +We can also ssh to Kubernetes node to take a look at the training result. + +``` +[root@paddle-demo-let02 paddle_output]# ll +total 60 +drwxr-xr-x 2 root root 4096 Oct 31 11:20 pass-00000 +drwxr-xr-x 2 root root 4096 Oct 31 11:20 pass-00001 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00002 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00003 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00004 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00005 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00006 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00007 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00008 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00009 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00010 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00011 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00012 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00013 +drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00014 +``` From c62d5b14a5c8343e455a21fe7c3cf01d14acd1eb Mon Sep 17 00:00:00 2001 From: zhouti Date: Wed, 23 Nov 2016 17:03:09 +0800 Subject: [PATCH 002/119] Added PaddlePaddle on AWS with Kubernetes documentation. --- .../paddlepaddle_on_aws_with_kubernetes.md | 306 ++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md diff --git a/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md b/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md new file mode 100644 index 0000000000..e2495055de --- /dev/null +++ b/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md @@ -0,0 +1,306 @@ +#PaddlePaddle on AWS with Kubernetes + +##Prerequisites + +You need an Amazon account and your user account needs the following privileges to continue: + +* AmazonEC2FullAccess +* AmazonS3FullAccess +* AmazonRoute53FullAccess +* AmazonRoute53DomainsFullAccess +* AmazonVPCFullAccess +* IAMUserSSHKeys +* IAMFullAccess +* NetworkAdministrator + +If you are not in Unites States, we also recommend creating a jump server instance with default amazon AMI in the same available zone as your cluster, otherwise there will be some issue on creating the cluster. + + +##For people new to Kubernetes and AWS + +If you are new to Kubernetes or AWS and just want to run PaddlePaddle, you can follow these steps to start up a new cluster. + +###AWS Login + +First configure your aws account information: + +``` +aws configure + +``` +Fill in the required fields: + +``` +AWS Access Key ID: YOUR_ACCESS_KEY_ID +AWS Secrete Access Key: YOUR_SECRETE_ACCESS_KEY +Default region name: us-west-2 +Default output format: json + +``` + +###Cluster Start Up +And then type the following command: + +``` +export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash + +``` + + +This process takes about 5 to 10 minutes. + +Once the cluster is up, the IP addresses of your master and node(s) will be printed, as well as information about the default services running in the cluster (monitoring, logging, dns). + +User credentials and security tokens are written in `~/.kube/config`, they will be necessary to use the CLI or the HTTP Basic Auth. + + +``` +[ec2-user@ip-172-31-24-50 ~]$ export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash +'kubernetes' directory already exist. Should we skip download step and start to create cluster based on it? [Y]/n +Skipping download step. +Creating a kubernetes on aws... +... Starting cluster in us-west-2a using provider aws +... calling verify-prereqs +... calling kube-up +Starting cluster using os distro: jessie +Uploading to Amazon S3 ++++ Staging server tars to S3 Storage: kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel +upload: ../../../tmp/kubernetes.7nMCAR/s3/bootstrap-script to s3://kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/bootstrap-script +Uploaded server tars: + SERVER_BINARY_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/kubernetes-server-linux-amd64.tar.gz + SALT_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/kubernetes-salt.tar.gz + BOOTSTRAP_SCRIPT_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/bootstrap-script +INSTANCEPROFILE arn:aws:iam::525016323257:instance-profile/kubernetes-master 2016-11-22T05:20:41Z AIPAJWBAGNSEHM4CILHDY kubernetes-master / +ROLES arn:aws:iam::525016323257:role/kubernetes-master 2016-11-22T05:20:39Z / AROAJW3VKVVQ5MZSTTJ5O kubernetes-master +ASSUMEROLEPOLICYDOCUMENT 2012-10-17 +STATEMENT sts:AssumeRole Allow +PRINCIPAL ec2.amazonaws.com +INSTANCEPROFILE arn:aws:iam::525016323257:instance-profile/kubernetes-minion 2016-11-22T05:20:45Z AIPAIYVABOPWQZZX5EN5W kubernetes-minion / +ROLES arn:aws:iam::525016323257:role/kubernetes-minion 2016-11-22T05:20:43Z / AROAJKDVM7XQNZ4JGVKNO kubernetes-minion +ASSUMEROLEPOLICYDOCUMENT 2012-10-17 +STATEMENT sts:AssumeRole Allow +PRINCIPAL ec2.amazonaws.com +Using SSH key with (AWS) fingerprint: 08:9f:6b:82:3d:b5:ba:a0:f3:db:ab:94:1b:a7:a4:c7 +Creating vpc. +Adding tag to vpc-fad1139d: Name=kubernetes-vpc +Adding tag to vpc-fad1139d: KubernetesCluster=kubernetes +Using VPC vpc-fad1139d +Adding tag to dopt-e43a7180: Name=kubernetes-dhcp-option-set +Adding tag to dopt-e43a7180: KubernetesCluster=kubernetes +Using DHCP option set dopt-e43a7180 +Creating subnet. +Adding tag to subnet-fc16fa9b: KubernetesCluster=kubernetes +Using subnet subnet-fc16fa9b +Creating Internet Gateway. +Using Internet Gateway igw-fc0d9398 +Associating route table. +Creating route table +Adding tag to rtb-bd8512da: KubernetesCluster=kubernetes +Associating route table rtb-bd8512da to subnet subnet-fc16fa9b +Adding route to route table rtb-bd8512da +Using Route Table rtb-bd8512da +Creating master security group. +Creating security group kubernetes-master-kubernetes. +Adding tag to sg-d9280ba0: KubernetesCluster=kubernetes +Creating minion security group. +Creating security group kubernetes-minion-kubernetes. +Adding tag to sg-dc280ba5: KubernetesCluster=kubernetes +Using master security group: kubernetes-master-kubernetes sg-d9280ba0 +Using minion security group: kubernetes-minion-kubernetes sg-dc280ba5 +Creating master disk: size 20GB, type gp2 +Adding tag to vol-04d71a810478dec0d: Name=kubernetes-master-pd +Adding tag to vol-04d71a810478dec0d: KubernetesCluster=kubernetes +Allocated Elastic IP for master: 35.162.175.115 +Adding tag to vol-04d71a810478dec0d: kubernetes.io/master-ip=35.162.175.115 +Generating certs for alternate-names: IP:35.162.175.115,IP:172.20.0.9,IP:10.0.0.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,DNS:kubernetes-master +Starting Master +Adding tag to i-042488375c2ca1e3e: Name=kubernetes-master +Adding tag to i-042488375c2ca1e3e: Role=kubernetes-master +Adding tag to i-042488375c2ca1e3e: KubernetesCluster=kubernetes +Waiting for master to be ready +Attempt 1 to check for master nodeWaiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... +Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... +Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... +Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... +Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Sleeping for 3 seconds... + [master running] +Attaching IP 35.162.175.115 to instance i-042488375c2ca1e3e +Attaching persistent data volume (vol-04d71a810478dec0d) to master +2016-11-23T02:14:59.645Z /dev/sdb i-042488375c2ca1e3e attaching vol-04d71a810478dec0d +cluster "aws_kubernetes" set. +user "aws_kubernetes" set. +context "aws_kubernetes" set. +switched to context "aws_kubernetes". +user "aws_kubernetes-basic-auth" set. +Wrote config for aws_kubernetes to /home/ec2-user/.kube/config +Creating minion configuration +Creating autoscaling group + 0 minions started; waiting + 0 minions started; waiting + 0 minions started; waiting + 0 minions started; waiting + 2 minions started; ready +Waiting for cluster initialization. + + This will continually check to see if the API for kubernetes is reachable. + This might loop forever if there was some uncaught error during start + up. + +.......................................................................................................................................................................................................................Kubernetes cluster created. +Sanity checking cluster... +Attempt 1 to check Docker on node @ 35.164.79.249 ...working +Attempt 1 to check Docker on node @ 35.164.83.190 ...working + +Kubernetes cluster is running. The master is running at: + + https://35.162.175.115 + +The user name and password to use is located in /home/ec2-user/.kube/config. + +... calling validate-cluster +Waiting for 2 ready nodes. 0 ready nodes, 2 registered. Retrying. +Waiting for 2 ready nodes. 1 ready nodes, 2 registered. Retrying. +Waiting for 2 ready nodes. 1 ready nodes, 2 registered. Retrying. +Found 2 node(s). +NAME STATUS AGE +ip-172-20-0-23.us-west-2.compute.internal Ready 54s +ip-172-20-0-24.us-west-2.compute.internal Ready 52s +Validate output: +NAME STATUS MESSAGE ERROR +scheduler Healthy ok +controller-manager Healthy ok +etcd-1 Healthy {"health": "true"} +etcd-0 Healthy {"health": "true"} +Cluster validation succeeded +Done, listing cluster services: + +Kubernetes master is running at https://35.162.175.115 +Elasticsearch is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/elasticsearch-logging +Heapster is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/heapster +Kibana is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kibana-logging +KubeDNS is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kube-dns +kubernetes-dashboard is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard +Grafana is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana +InfluxDB is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb + +To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. + +Kubernetes binaries at /home/ec2-user/kubernetes/cluster/ +You may want to add this directory to your PATH in $HOME/.profile +Installation successful! +``` + + +By default, the script will provision a new VPC and a 4 node k8s cluster in us-west-2a (Oregon) with EC2 instances running on Debian. You can override the variables defined in `/cluster/config-default.sh` to change this behavior as follows: + +``` +export KUBE_AWS_ZONE=us-west-2a +export NUM_NODES=2 +export MASTER_SIZE=m3.medium +export NODE_SIZE=m3.medium +export AWS_S3_REGION=us-west-2a +export AWS_S3_BUCKET=mycompany-kubernetes-artifacts +export KUBE_AWS_INSTANCE_PREFIX=k8s +... + +``` +And then concate the kubernetes binaries directory into PATH: + +``` +export PATH=/platforms/linux/amd64:$PATH + +``` +Now you can use administration tool kubectl to operate the cluster. +By default, kubectl will use the kubeconfig file generated during the cluster startup for authenticating against the API, the location is in `~/.kube/config`. + +For running PaddlePaddle training with Kubernetes on AWS, you can refer to [this article](https://github.com/drinktee/Paddle/blob/k8s/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md). + + +###Cluster Tear Down +If you want to tear down the running cluster: + +``` +export KUBERNETES_PROVIDER=aws; /cluster/kube-down.sh +``` + +This process takes about 2 to 5 minutes. + +``` +[ec2-user@ip-172-31-24-50 ~]$ export KUBERNETES_PROVIDER=aws; ./kubernetes/cluster/kube-down.sh +Bringing down cluster using provider: aws +Deleting instances in VPC: vpc-fad1139d +Deleting auto-scaling group: kubernetes-minion-group-us-west-2a +Deleting auto-scaling launch configuration: kubernetes-minion-group-us-west-2a +Deleting auto-scaling group: kubernetes-minion-group-us-west-2a +Waiting for instances to be deleted +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Sleeping for 3 seconds... +All instances deleted +Releasing Elastic IP: 35.162.175.115 +Deleting volume vol-04d71a810478dec0d +Cleaning up resources in VPC: vpc-fad1139d +Cleaning up security group: sg-d9280ba0 +Cleaning up security group: sg-dc280ba5 +Deleting security group: sg-d9280ba0 +Deleting security group: sg-dc280ba5 +Deleting VPC: vpc-fad1139d +Done +``` + + +## For experts with Kubernetes and AWS + +Sometimes we might need to create or manage the cluster on AWS manually with limited privileges, so here we will explain more on what’s going on with the Kubernetes setup script. + +### Some Presumptions + +* Instances run on Debian, the official IAM, and the filesystem is aufs instead of ext4. +* Kubernetes node use instance storage, no EBS get mounted. Master use a persistent volume for etcd. +* Nodes are running in an Auto Scaling Group on AWS, auto-scaling itself is disabled, but if some node get terminated, it will launch another node instead. +* For networking, we use ip-per-pod model here, each pod get assigned a /24 CIDR. And the whole vpc is a /16 CIDR, No overlay network at this moment, we will add Calico solution later on. +* When you create a service with Type=LoadBalancer, Kubernetes will create and ELB, and create a security group for the ELB. +* Kube-proxy sets up two IAM roles, one for master called kubernetes-master, one for nodes called kubernetes-node. +* All AWS resources are tagged with a tag named "KubernetesCluster", with a value that is the unique cluster-id. + + +###Script Details + +* Create an s3 bucket for binaries and scripts. +* Create two iam roles: kubernetes-master, kubernetes-node. +* Create an AWS SSH key named kubernetes-YOUR_RSA_FINGERPRINT. +* Create a vpc with 172.20.0.0/16 CIDR, and enables dns-support and dns-hostnames options in vpc settings. +* Create Internet gateway, route table, a subnet with CIDR of 172.20.0.0/24, and associate the subnet to the route table. +* Create and configure security group for master and nodes. +* Create an EBS for master, it will be attached after the master node get up. +* Launch the master with fixed ip address 172.20.0.9, and the node is initialized with Salt script, all the components get started as docker containers. +* Create an auto-scaling group, it has the min and max size, it can be changed by using aws api or console, it will auto launch the kubernetes node and configure itself, connect to master, assign an internal CIDR, and the master configures the route table with the assigned CIDR. + + + From 9bb31dfbd434b24c5a51349b2e1eeeb57b75e3cf Mon Sep 17 00:00:00 2001 From: zhouti Date: Wed, 7 Dec 2016 22:47:16 +0800 Subject: [PATCH 003/119] Revised PaddlePaddle on AWS with Kubernetes documentation. --- .../paddlepaddle_on_aws_with_kubernetes.md | 92 ++++++++++++++++++- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md b/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md index e2495055de..920608c562 100644 --- a/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md +++ b/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md @@ -8,6 +8,7 @@ You need an Amazon account and your user account needs the following privileges * AmazonS3FullAccess * AmazonRoute53FullAccess * AmazonRoute53DomainsFullAccess +* AmazonElasticFileSystemFullAccess * AmazonVPCFullAccess * IAMUserSSHKeys * IAMFullAccess @@ -22,7 +23,7 @@ If you are new to Kubernetes or AWS and just want to run PaddlePaddle, you can f ###AWS Login -First configure your aws account information: +First configure your AWS account information: ``` aws configure @@ -38,7 +39,7 @@ Default output format: json ``` -###Cluster Start Up +###Kubernetes Cluster Start Up And then type the following command: ``` @@ -202,7 +203,7 @@ By default, the script will provision a new VPC and a 4 node k8s cluster in us-w export KUBE_AWS_ZONE=us-west-2a export NUM_NODES=2 export MASTER_SIZE=m3.medium -export NODE_SIZE=m3.medium +export NODE_SIZE=m3.large export AWS_S3_REGION=us-west-2a export AWS_S3_BUCKET=mycompany-kubernetes-artifacts export KUBE_AWS_INSTANCE_PREFIX=k8s @@ -218,10 +219,91 @@ export PATH=/platforms/linux/amd64:$PATH Now you can use administration tool kubectl to operate the cluster. By default, kubectl will use the kubeconfig file generated during the cluster startup for authenticating against the API, the location is in `~/.kube/config`. -For running PaddlePaddle training with Kubernetes on AWS, you can refer to [this article](https://github.com/drinktee/Paddle/blob/k8s/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md). +###Setup PaddlePaddle Environment on AWS +For the design of running PaddlePaddle on Kubernetes, you really need to read [this article](https://github.com/drinktee/Paddle/blob/k8s/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md) first. -###Cluster Tear Down + +For sharing the training data across all the Kubernetes nodes, we use EFS (Elastic File System) in AWS. Ceph might be a better solution, but it requires high version of Linux kernel that might not be stable enough at this moment. We haven't automated the EFS setup at this moment, so please do the following steps: + + +1. Make sure you add the AmazonElasticFileSystemFullAccess policy into your AWS account. + +2. Create the Elastic File System in AWS console, and attach the Kubernetes VPC with it. + +3. Modify the Kubernetes security group, add additional inbound policy "All TCP TCP 0 - 65535 0.0.0.0/0" for Kubernetes default VPC security group. + +4. Follow the EC2 mount instruction to mount the disk onto all the Kubernetes nodes, we recommend to mount EFS disk onto ~/efs. + + +And now you can place your training data onto the EFS, you should follow [this article](https://github.com/drinktee/Paddle/blob/k8s/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md) to locate your data. + +###Start PaddlePaddle Training Demo on AWS + +After setting up all the steps on AWS, We can start up our PaddlePaddle training recommendation demo by using: + +``` +kubectl create -f job.yaml +``` + +The yaml file content is as follows: + +``` +apiVersion: batch/v1 +kind: Job +metadata: + name: paddle-cluster-job +spec: + parallelism: 3 + completions: 3 + template: + metadata: + name: paddle-cluster-job + spec: + volumes: + - name: jobpath + hostPath: + path: /home/admin/efs + containers: + - name: trainer + image: drinkcode/paddle:k8s-job + command: ["bin/bash", "-c", "/root/start.sh"] + env: + - name: JOB_NAME + value: paddle-cluster-job + - name: JOB_PATH + value: /home/jobpath + - name: JOB_NAMESPACE + value: default + - name: TRAIN_CONFIG_DIR + value: recommendation + - name: CONF_PADDLE_NIC + value: eth0 + - name: CONF_PADDLE_PORT + value: "7164" + - name: CONF_PADDLE_PORTS_NUM + value: "2" + - name: CONF_PADDLE_PORTS_NUM_SPARSE + value: "2" + - name: CONF_PADDLE_GRADIENT_NUM + value: "3" + volumeMounts: + - name: jobpath + mountPath: /home/jobpath + ports: + - name: jobport + hostPort: 30001 + containerPort: 30001 + restartPolicy: Never + +``` +It will generate three PaddlePaddle job runing on distributed Kubernetes nodes, and all the training result will be written into EFS. + +We've made an experiment of running this PaddlePaddle recommendation training demo on three 2 core 8 GB machine (m3.large), and it took 8 hours to generate 10 models. + + + +###Kubernetes Cluster Tear Down If you want to tear down the running cluster: ``` From 70ee18efb11fa30cbf183e7a569fdf6303ea6b79 Mon Sep 17 00:00:00 2001 From: water1981 Date: Mon, 12 Dec 2016 18:16:24 +0800 Subject: [PATCH 004/119] Create index_cn.md --- doc/tutorials/embedding_model/index_cn.md | 139 ++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 doc/tutorials/embedding_model/index_cn.md diff --git a/doc/tutorials/embedding_model/index_cn.md b/doc/tutorials/embedding_model/index_cn.md new file mode 100644 index 0000000000..f0ed8a65cc --- /dev/null +++ b/doc/tutorials/embedding_model/index_cn.md @@ -0,0 +1,139 @@ +# 中文词向量模型的使用 # +---------- +本文档介绍如何在PaddlePaddle平台上,使用预训练的标准格式词向量模型。 + +在此感谢 @lipeng 提出的代码需求,并给出的相关模型格式的定义。 + +## 介绍 ### +### 中文字典 ### +我们的字典采用内部的分词工具对百度知道和百度百科的语料进行分词后产生。分词风格如下: "《红楼梦》"将被分为 "《","红楼梦","》",和 "《红楼梦》"。字典采用UTF8编码,输出有2列:词本身和词频。字典共包含 3206325个词和3个特殊标记: + - ``: 分词序列的开始 + - ``: 分词序列的结束 + - ``: 未知词 + +### 中文词向量的预训练模型 ### +如下图,遵循文章 [A Neural Probabilistic Language Model](http://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf)中介绍的方法,我们的词向量模型的结构采用:6元上下文作为输入层->全连接层->softmax层 。我们的字典包含4个维度的词向量编码,分别为:32维、64维、128维和256维。 +
![](./neural-n-gram-model.png)
+
Figure 1. neural-n-gram-model
+ +### 下载和数据抽取 ### +运行以下的命令下载和获取我们的字典和预训练模型: + + cd $PADDLE_ROOT/demo/model_zoo/embedding + ./pre_DictAndModel.sh + +## 中文短语改写的例子 ## +以下示范如何使用预训练的中文字典和词向量模型进行短语改写。 + +### 数据的准备和预处理 ### +首先,运行以下的命令下载数据集。该数据集(utf8编码)包含20个训练样例,5个测试样例和2个生成式样例。 + + cd $PADDLE_ROOT/demo/seqToseq/data + ./paraphrase_data.sh + +第二步,将数据处理成规范格式,在训练数集上训练生成词向量字典(数据将保存在 `$PADDLE_SOURCE_ROOT/demo/seqToseq/data/pre-paraphrase`): + + cd $PADDLE_ROOT/demo/seqToseq/ + python preprocess.py -i data/paraphrase [--mergeDict] + +- 其中,如果使用`--mergeDict`选项,源语言短语和目标语言短语的字典将被合并(源语言和目标语言共享相同的编码字典)。本实例中,源语言和目标语言都是相同的语言,因此可以使用该选项。 + + +### 使用用户指定的词向量字典 ### +从用户指定的词向量字典中抽取模型的命令如下: + + cd $PADDLE_ROOT/demo/model_zoo/embedding + python extract_para.py --preModel PREMODEL --preDict PREDICT --usrModel USRMODEL--usrDict USRDICT -d DIM + +- `--preModel PREMODEL`: 预训练词向量字典模型的名字 +- `--preDict PREDICT`: 预训练(词向量)字典的名字 +- `--usrModel USRMODEL`: (用户指定的)待抽取的词向量模型的名字 +- `--usrDict USRDICT`: 用户指定的字典的名字 +- `-d DIM`: 参数(词向量)的维度 + +此处,你也可以简单的运行以下的命令: + + cd $PADDLE_ROOT/demo/seqToseq/data/ + ./paraphrase_model.sh + +运行成功以后,你将会看到以下的模型结构: + + paraphrase_model + |--- _source_language_embedding + |--- _target_language_embedding + +### 在PaddlePaddle平台训练模型 ### +首先,配置模型文件,配置如下(可以参考保存在 `demo/seqToseq/paraphrase/train.conf`的配置): + + from seqToseq_net import * + is_generating = False + + ################## Data Definition ##################### + train_conf = seq_to_seq_data(data_dir = "./data/pre-paraphrase", + job_mode = job_mode) + + ############## Algorithm Configuration ################## + settings( + learning_method = AdamOptimizer(), + batch_size = 50, + learning_rate = 5e-4) + + ################# Network configure ##################### + gru_encoder_decoder(train_conf, is_generating, word_vector_dim = 32) + +这个配置与`demo/seqToseq/translation/train.conf` 基本相同 + +然后,使用以下命令进行模型训练: + + cd $PADDLE_SOURCE_ROOT/demo/seqToseq/paraphrase + ./train.sh + +其中,`train.sh` 与`demo/seqToseq/translation/train.sh` 基本相同,只有2个配置不一样: + +- `--init_model_path`: 初始化模型的路径配置为`data/paraphrase_modeldata/paraphrase_model` +- `--load_missing_parameter_strategy`: 如果参数模型文件缺失,初始化时,除词向量模型外的参数将使用正态分布 + +如果用户想要了解详细的数据集的格式、模型的结构和训练过程,请查看 [Text generation Tutorial](../text_generation/text_generation.md). + +## 可选功能 ## +### 观测词向量 +PaddlePaddle 平台为想观测词向量的用户提供了将二进制词向量模型转换为文本模型的功能: + + cd $PADDLE_ROOT/demo/model_zoo/embedding + python paraconvert.py --b2t -i INPUT -o OUTPUT -d DIM + +- `-i INPUT`: 输入的(二进制)词向量模型名称 +- `-o OUTPUT`: 输出的文本模型名称 +- `-d DIM`: (词向量)参数维度 + +运行完以上命令,用户可以在输出的文本模型中看到: + + 0,4,32156096 + -0.7845433,1.1937413,-0.1704215,0.4154715,0.9566584,-0.5558153,-0.2503305, ...... + 0.0000909,0.0009465,-0.0008813,-0.0008428,0.0007879,0.0000183,0.0001984, ...... + ...... + +- 其中,第一行是`PaddlePaddle` 输出文件的格式说明,包含3个属性:: + - `PaddlePaddle`的版本号,本例中为0 + - 浮点数占用的字节数,本例中为4 + - 总计的参数个数,本例中为32,156,096 +- 其余行是(词向量)参数行(假设词向量维度为32) + - 每行打印32个参数以','分隔 + - 共有32,156,096/32 = 1,004,877行,也就是说,模型共包含1,004,877个被向量化的词 + +### 词向量模型的修正 +`PaddlePaddle` 为想修正词向量模型的用户提供了将文本词向量模型转换为二进制模型的命令: + + cd $PADDLE_ROOT/demo/model_zoo/embedding + python paraconvert.py --t2b -i INPUT -o OUTPUT + +- `-i INPUT`: 输入的文本词向量模型名称 +- `-o OUTPUT`: 输出的二进制词向量模型名称 + +请注意,输入的文本格式如下: + + -0.7845433,1.1937413,-0.1704215,0.4154715,0.9566584,-0.5558153,-0.2503305, ...... + 0.0000909,0.0009465,-0.0008813,-0.0008428,0.0007879,0.0000183,0.0001984, ...... + ...... +- 输入文本中没有头部(格式说明)行 +- (输入文本)每行存储一个词,以逗号','分隔 From a65578586e6b32ee1ebfcc6c5a19537708a70ead Mon Sep 17 00:00:00 2001 From: zhouti Date: Tue, 13 Dec 2016 19:54:50 +0800 Subject: [PATCH 005/119] Translated and added PaddlePaddle on Kubernetes part in English. --- doc/cluster/aws/add_security_group.png | Bin 0 -> 237481 bytes doc/cluster/aws/create_efs.png | Bin 0 -> 250321 bytes doc/cluster/aws/efs_mount.png | Bin 0 -> 230609 bytes doc/cluster/aws/managed_policy.png | Bin 0 -> 247321 bytes .../paddlepaddle_on_aws_with_kubernetes.md | 491 +++++++++++++----- 5 files changed, 371 insertions(+), 120 deletions(-) create mode 100644 doc/cluster/aws/add_security_group.png create mode 100644 doc/cluster/aws/create_efs.png create mode 100644 doc/cluster/aws/efs_mount.png create mode 100644 doc/cluster/aws/managed_policy.png diff --git a/doc/cluster/aws/add_security_group.png b/doc/cluster/aws/add_security_group.png new file mode 100644 index 0000000000000000000000000000000000000000..50eed4c6573a18d6ae0f9df9bd6a3cae05493e3c GIT binary patch literal 237481 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

;Z>aHpm|~z;JVY=H3+B8 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

t#8Ut1fSE$uCAV3y&iBGMr#s5{iPh* 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�fuy`r+Bc*i*N@AeAQ$RbL;cE`S<@zM}eCsaBrq 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≤N&{|>|dR=DF5|{ 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
!kgl!T zWkW7A>;_F>qt@g`WNGkT1B-a!&Pi~9<3EyY zwOa@Kp?yy0{(~nkU2GA|@`Va7x+~@r$J#H31bVCXDVHB-Vp$;2F zTz)^rao_jgXDmj=td@4jCWIxln^T(!EXFS%WLCPHpWwB%!ya@WRUP!w9riwPL_s^o z=2X)`J{8L(MRd=lUsRQ)M2@b7^)z8eG&@ppUwA`OoOC?&!j7XW0yC+;G0c^Ir=+Ge z@xsb>IDKnUR}T(_JoN9{FZ1|#0TteQb`vZ*&*q04HQN zaQ9!CueX%&gr$>JFUVBS_kcK1P{61^a6kuNuqmS7yXPxc%?#eUvcBvQi#W9s?oJCxgoaK!v@6KYTm7`+?@bPZ@V?* zyh@Viq9Ycv)teV$p(jg#9@-w8+%DS~9j+;}l3O?EK;7!*yHQsBL zn@NgvkYhdqS;cXbxKm~PyQz`Aw?Z|~M0obazN-mSfsSt)47QNYY-o^@^~_^Gd`U3W zwuzv!b&n}yJTBp7pUaZW!pKvicmbqp=CnuqV-y^#R4jDi_NKg$?&Q>Gxl11i-?a?T zQE+4=Fjtb9))LNTp?n#GO|&O>n*_HHEsd}RETW$YNk$1BXqSw|QZGDpa8LYvs4I3p3kMgVi@|r8Wz0||s@TCv)4f=t#k4W!EE8M4lpF~sCpH3&vGBP-9MvXa=-Prwu4 zsZR{rUQ@I53)pA+qjt7kGj1;CFh zDdh!8rWS713MV*cb&Q1HmLwHGkvH7sk(CjqPxjr{Y*+RsGlKEu)xsQQDDvhV3fv&P zql#`AgZ6A*DaIAaRF7fxiA>D7_~l)YRdPU^=OXH<<$g*JYm!Tz5PVY;Qk6D3DR9OQ zUuo8AU~6uC)IstrTyh@rFD&68ghZS4&@lsH%)z8+e>9JW2-gecwaZ1isDs;$zt zX}pXCS#YZwQ51pFyp6CvFXivaY`ym>j;A_aG0wsfgbxdG%Z+;Hve4@dbFjp&5y&^zLjJkZ|@c=vHQAaffq)mFMc_T7O#bLCjs$OZ* z!^q~R$hL?et(R&KJ>G>&dV@6G9%)g7;BlGK8qpF`1wQl2#0Mzi8>a1hIS3#8vxaBl z@TKkhK@2gXkf~B@Wm-vSfA^S-h~f2=?MqZPt$fsk^b~6M#}wmQci6V(;?aV4+qULh z-u5!sBIeuRiCVQ~QQKk0t;uZjo&f&gAF(^{;=NUA>@_5)P=gXb;UnZ-^<6Obm$j># zCTo%ZZGvyHrr!gyibEkPeG-P?XkEcE^Zf$Mf)|n;L&6JJaH?K{05cPNbMQ7cBkm$Q zcft!8zQnapdNpbB$~!B&P#&GFck2Ad&l1vB>`n);#9Io#zy#gDd9|PKv5~8ChD}Qb zuLPsXtd%g^M*_ggIkNZP=HOl2-=|IMZo+3qTD=K}O6;{NGaGdJamAU)${Z&vvI$04>yxsK~fo=Z{}f1VgP1}m$>c|O~VFSQ)8XICS) z#Gb+^+%?VP6=4d-H#OT#D7c(Io@E}hz>pJ|1B3tx7*L$uZj)$cZ422admw(7c&Y_y zeZ=BaiZDH7lq567AHA$`uMR^bmm9$9s0j_aG8CTnDN$Vg49Hp> zNr%hEL6N=Q6#Yo>l|Pw*jI<@5UP+QG+(9BduPB>vXfg!LP z5M?~4igrjW<8b$uJ!Qbg`}RlOWx5fCQ>w58k)f|YPa9a7VBf$mMb-6euwVF=EPw}h2nwSB# z75edOHD*6eN&dxNGg1Sj(cF!9@!t*MyWxYT$&JCFW}rI|k^dsi|N1BH98f%96rqm% zt4BY3sJjIi{N(eMYooucE`e!Y2CUhfs_VZVR1-LYUBG1hdR2?T)q(ntUDSR(sK8<{D4QBg ze!?$T^&e6FM^yh2)&F0#Dplw66`=q0YzVbCQ4c+wpR+}IriVuaEcI!3ijr!0%t?pkA|b52?oB*d<7i zx?{_EoOd0^2DmbPXxD|GaY>`d;NA#G+_wXySAErWxKb&`>0N^0guSX}v5~2!3qm~- z=~U^&=7gDeC@|^xj$xZ5I;M_eIMQ|`#t>bSPM7tQuc|x!gMGG<88_PG5fVpN)X_MNtK1xIj8NAZu~*3S+OIo76z|yRL;{MY zMNVe2aQ6$Sd)*WT2X+?>j1vZKxa3WAPqc}8@-`LT@~#nTs5kj}8XI||4Lu2K{h#O?T3QYB?y z-HTIxhk!LYC*=D{Y9*I#mAI!elp3;k8^dD63raD2P^Y8$aU<8HqfBP`AGNvE?cD6;82|Q@zk*9(6_>~>vlTo5&v_F%)?OWSg@E1m_=-m;LN98V)TDfC z+m_4gBpzB@Pk5yal2I+%&E=4*-0e=eG`2SVBWHu06jn&Zqpz@<)Ba<5_}cB6g8{d} z#hNt!*L7&`Z=GziydLICb)E{s1O;(`p3Pb3Nv>7Q(&GMElbu6|IkCObtkGlKK9)cAT1K;RM`H0Mak27%J0j)k_{Zac$;bnkA3squczcMN6|T5Y6cRQ{c6X;HQ47d9mIrQ%PG`f32 zAfCrpYH9=WzW(J`U)6IbAm`C{&k|w&;n)A_26*7Uf+yQS9x%4d0A`T822b_^Il@h% zOFe%g)e^i9%RuF8@x5-i+b~AnfyaijF`eR{x zYKLF5m1h+JNV4U#HM1iQQ?S#Kr$wiZTQigFj&yrGt~o$*2^!&u6kNm1apn(kFcU7& zj|5b!Dsw=Tt9G}meq$$!SuT!4B>ISN{sx$EjxkHB8pO>xwQ4R#)b5+ThK>H0&rVkv zFI`&$2;>~aA;{0zKJjVc6+^&qseu0L1wX4X(lEh0>c3IZ(*->qzs6BM{t&xPcVyF6 z7r1R`4o77mL3?Jx$<0iXGdL57OBUC^xsZq>Ojx|7;KH#Skz}>IGX3inU<~|H+$;Bw zmTIv%&_&gx=K|?>9-g~yv;wb8Z_8Ui)AeF(;-?9$^! z%l9-c!Vizo;5j0y8&Dn;U$pO^V~=+BM(eMD>IQbtbuv#CfVIK?&u<_cqcyO8&83xb-&xGIT28!S?j!0zPLLRvX*2MSe6KAmPi~DR#LG0 zl*la_F>boVcK2Hu{c}T{Rm4fsM%lc1CcgUY%4oK1he=ZXJGGoP9Cl#~c!g2rRY(p) z9~wuKHJ#0N9175uwbl;Ov})o2L=tesA((OH{bO$&q`l!51I-0n`_xLwZn_U$Ti-I( ztKF1Y-M&kqYEYcR*i+4=1t?v2*W~A|8MPAH*R}xF38Dk2_EGqArTw5VY9f$e6XciP z5njo`;EFD60lhuaSZqnNGfT_?lO-;8LXL6df_Pi83Fp{=eSw}pH&1_>{n+mN`4|;B zBQ&*-7~S4a)MB0anLrDmfl{_}L=uE?uov&MJ1f3-ea009Cmwkm%+N*QC`?RNnklb$lQ0+OCW-_`;)d5KpVI?273q0swfw^^blrY}{g;7u1C2Q4DILCVn;c4ERd;OSw$ zQ?(iI?UsFbq<)B4*{m{vlIs*s-kes+V9Q3yaJj0u?AFuisJkVhay=RtX;m(OO3aPl z$!gxKGj|^-n7%d5yM~8`=oP6{6DQH z`Ra($g#IiCYPD=z=-MsD;}p1BJ%l;-Baz1gT!AI7ilDT3E0Xsb7HERUH&5R0QxaJj zuz#_6Q=!Nj8KE9|t~?3d8ketSQL3hze>k*sO{U#p7&1&<3=ufa;xOQ6S#i!GQK@A= z!#sZtR4ZuSYwiT~kP}rhiSQyE6(R9~=-4~;E{^t!$@n{yWfOqN-!`%4vD0Uj78Kse zqh6phkyE@sY*#QJFWM?HxRu$_NTxeZHD~}<*{WloM&xwve4?FFwb0{50P~-1Ym2xM zXXg(%D{JD}khKPm&C7Sk+T7a-_#7U+7;gtPg9T|pEEb^3#1NE~&}5}n24~rJ@(=S_ z@TJnFP2yMwdzM>beqBu<=^TaIz%Ko=#Wz;K%50ltyk9U_$;*J)tBOT!Z)HfSNG_^*J^#6BpE{;f zwVihh6eFQ%rg7@qK^WnpWnZ`GBc->+kT^a zX{n&LhVVh#nk$y$&Gt>Txa{GGeJ(%^dh1DqOf+mucGOzIQl&+<8;?5CTPM%_c^1Iv z^EPIrlbD73n;C(V2Y(-%^R2&e?kaZ~jN%S?r|A$VPt1VM@g|uHpX-ZRM{;?(g!Xts;lXL+1vtAs0U639 zb_Ra5Hg-$*s5EM3uiMk3-*11Yyr#4~ulh~)HIRaxSERrX>#D{jhyO5Cl_ted0Ie6B zcJFy?$?ti@+xOj5oKmbkB=mM3%+XQ9CCUja`>#f?DY=EeZWf*6K>}*a)+;DsUmO2W zxFZ@!WD!dGAk%X{bMBJ^0-Q^sjVmyob6#;B!=NE1ryU2}5qn)uZB&Ternkq!EbqUs zGl1QlsG9kZIPZ9#`>wTu$cJawLn(RJFNYv2 z+&~U9@@O~YNF&Ewz~EKL0VhkA($BJesTV)#J-VE2%!h|!cgLk{bJ(pyGy1bKa9&<# z&rVEzweEc^u8a_go4i*ObVs|*?QEIxMUUU#?hMzDw`rn4u2^|uUd~05inXzonU7yF5 zyzGn*W`mXieo;th)I!zeu;_ztpT?V3LlciW*GHkQ+Qpdqe0)h~=Xu8L-AwzJq;D@u z0=g75LUPH}J6Pj%%|+u=Qy9$fCphm$aA(G zAQfI=N|w#o9j1^OIxoFbRk>8Uy1jF?yAULX&D>ocA5N|v>hLni@026Y>h9hi0kKj$ z;6nErbPF*paP?abK1yDG9IWceq zi0?T(Ar7M&hfXqhY(-Eob>*#zaO?)xQBjO#Mr5}Y`i~IN9@V#jb5?iOGMBq|vM2KU zt}NNzL<}~IZx<>~WDBm)H|HZSeu+v>*yb%8K}mckj(^%)JxV-%aK_#F9HAR;RTxII z9nX{?78LcQGpMr?)F^B*pNTx3d5dI(#rh~h%b}#XZ^|XiGBsXaym|Z1B)#H&pOzDJ zyLHU(lY>X!2p*9}hX(uOD)*~ZTMw2fu$5v7QeBMmY9}OaLhfZ;%bp*+0iC}+Nw0jY z9(OdvSp0AMr|$`^;m2JgNY9`aQN1(3cE1mXKEfi7%!L0Bdv6&R<+`>H3jzWXibzO^ zbaxJI&<)b1bW2N1ER&G#P+$ z=f3K^&Nz<--rzCHEI zd~fQT<6(evo(V6|*j<1@i8pw*^t4-Ke%@?i3Swyc*bgh}i{sXNT5W}fSH`Tn!mbK@2o$$E){OGiaam2@ zY0~6gk~rBQJ}uopv4N#eQ6^h_`5-`|LfF?ejkF6wkvj4xm+ehsTxy$>Y??cSK-~{G z0m0TjYgO`8y&iUUJVFaK*HCq8SL5ausKqg^{wynZ=K#hP7IenG&n0}6AgxiEa|F2CiYBgYjYN;46jW`DynCV?mCvmOthr~xD>!MW6uT$ZWE7X2#aXkvJ(hxi zWKPm7-^fsWO=msZ(vC>brpTob{yHDHVbI6(W}??vvlmrMZ~9$%@I4mKbDx=88?;k5 z(%RiU?4w%pd0h>M%Jtw+09;KdgH6X?iABo~mQW{&!39BDfoveN3{(27qF4=9g2D)S zS86}yMF!c+l*x=VUNfWXC!-Rz>Uh>Y0h2%sCkL)dpAQX{^+aJkVE@rGcO9dtK6ChYxmHxUrNCc^xtIs^o*Mp~=BOI1V60djJEx-Fr#45dy!D=c-eAtjg*BTw6|6|g z+4`oTjL6R(C%NvgQ_KWz zIW;A;HG$0c=ovg2A?=|4t}|7axMaytqs1cAhbVP<6GHAMMi2GZ6JC45wOP~m@d_18 z@Km1I)X#c^=Vdt47`QZ3W^OxY_)=$TpMNJYfQn-aeyE72wyNeHBlC>FW-gdc}pW z`(XhK8Xld?Tf8o=>KRx%_e-6p_Df3KqofNJzdU0R>ER19;-U$V(LjsQ( zk?QD@m}+l^uHKbi^6O#iYQm1z^t59=;;o+J=F39asgg-eyi8|(K%v5KI&*EpBX-95EiQk7} zJtL^6$LH^T8(C>#PB!?wBGQvuTqgu zI5U3K!E%AYNzf_lkUH}#MU`{r&u)?n!DWPf6j(L^3*B+&g_z#Zr(;&NaZiKuvA!F? zQJY{F#B6R3WvJlf`ykyhI~~s^jZUlt8c@Ov*DZ0^Rkk(fD4d(^fFnFtKk;Pvbxzgu z$Zz>tFiC)~A{|CCI!{!AG>u>@9o|!g79}YMb2;(3tX?*=vs!l(KdUDUKZzCb6Oi!j zF$QVC2ygLIz*L+vt&%v8nOqL{LXWMw&ka|fqhw>U65vu3u_6ttobft02kCab5U5X> z1O=*zy4}e{k>U}YRUO&Mgo_uaXnH1vd*YDJK%zop*E^KIym4-N)rZ>>D`Kk5wD<7H z>0XxIYJwjDFK4%_j%UZ}>416z$sZR+R^>0`-1YzT{9{UGd|&V+_WPexSMjgkQ_B_Jq^P!^ii{_ zOvkbf0H9W9Iem&z`c^)26Aw)uKTH9)ftk&<;$8eovV-{7^?6f?61C0RG}(tV-J5)^ z_Y`Z_jOFFVlQ@}z#K$)6u2wiZEcLpHt2b{uQtcQ_XFRnn{|ukaS&Ji^+1z(3-rt_v z_KckQ?B!H((tNsm>Ws;VbMf`UNYkqAjmd=SL`1iipIH%uljteYL6^Lg7C)B~;oCw_ z#xDM#t0xe*N-N`yc|}4 z=w2*zfvtBY+OTe$5#cdni8%ENXp?ZzHVf3_Y1nw{bvc&1M!q~nFqQ^M+9i1alKDtH zSxUQg$`|z6#+GpfSdT$EIFsw@@v=P2XyQ)1jh)<-Lcv@zUh;__)9Jtp4(6CYp1<#a zd&Wq(L_&UAAc&`g8KSMWB6l`u7#6iUyd)7Sm; z@)x1pA6#t`IKF;k<*v#bn9;>dZFM%r^+4!}*H^oFdLU6F8&}mbdj)BahSu55>@cLfyQ(o+j&W@7L?iFp}w!Sp)8;s=&F-D;j#I z0hQCv_3=kEpvI)yG6OtLzzAB1Go4h~XXec=IQVhtH#iyBkvI13`C3QVlf_RZPL;)< zt+1X!>AFVxcZj#j^Z~jR~)}4^!tX84-ds4`j#@(W>ZRZH74paJ%f%N z*Gr~{%L0H;xV`GQ)a?eplh2mz|D|EtXSHdkddJN>Nba1ScX%M!F3B2Z^zb zo$K=R>C?B5;dHi3>op%F1DhqXr*xwh+?|3OXwqv6r`fVcJ>>D;gZxfP^aUu}?==o* zSZ@S5q%bFg?$o&|fzq|YV5$89PjRy5ovISm`y=APx^|%z@owBzjT06}pQTSMHq%Rx z`$xc&`Jgg1G+V8aAisT_T#zxhpFYP%DU=I`SSzZ2DFrG!jfXSq8G~#5C>^fYYR$|; zpV!i|1fFKc4ZsAjsEFULsgXvCRn=4_B2S5ysx|blbc-o=l;tnr)-CMTbm3*gVL) z>2YUkFZfNW9itb(+sNq zP*v*SO%a~{av~ZYAXg{~NU1De1IH^e#;EY6U3RR)p5-)Pr>Hdtw%v!cs!*P2rg{2o zl`j?WhU9daN6t!F@-*|F;;hh?hM;hoA&)`adq0Um+BN1Ipf&3 zyRLq^rJhO$|0|_#{5rBB|2T|A;)R|I>V63o32w}toz0G2NJR|J9vC?{SYU?kOOC8ApPYHJ(qQ3aP@hf`+lG&p^{&I}dj<`wO$4lgcQs zM?oP0_ANJhDuV>vuGWn>f7ws9yM_}`UBqDqE7msi&IzVw$)+Ehi^L+G0l8pXZX_fz zXJU%Ehc-$$Ld89JbYwji_aGLa*Gr0pv&F5LP~1*UsU&n`G_~+l8yFIx*N=Uh^~Aga zLpLYYY7;=Er1{GGHzwogDNMZCLKJVSlu&6|`sOpUqr02b#nb4%!hETqiVV$2DIga& zVbX82Hc=0VD%MY{cXnCCk*t&kYDP{$yX8gj9q;Pt1P9um`oH?Y5f8cOPPedzhqhUQ z&@BB&%T*6m_+lcTtW9rCW)d~7@$NP6`ZZ4=E0yxz3S@i>x86Hy$WMnsnxl9wjCq?P zH`-XTvT-it7|RWJ8BJ0)iL+tgoPx~HFw#SrSAN!7O&-#H*g``Wdw1fQmEPD*< z8BN6p)G~A^2E`TB5(sV*1=u$(pQzj_oFiQlnkgiqmcM0$YE86+ty-~Pl3b#K1%t^kFqUwHA zBfcXJOkMO737Qcx=>5>EM_G8RJsvE{vtJe%iK*$$*-ktM95z0kqtSYCK)} zmA#>pz}zM*>kmNPVCA^}S~D{FE%Rw{mw?zQZEqK66&GAZ`~fTSr%9J$s4yE2yO%Xb zxZLEEdb*O?oUau^&tIefJ6pGu6j`s=xO#g5O4n=Og3%1~B{n-Nb>70H(2|E;&)Dy? z*?}u6)=s&;=Sqw$fQnj;D++G3l(**FuAbamPl5~_IPyf*)B!1k=M@e0q&i5Qf5sYn~;3+r>f?>1s(i9uI5gR1noW-@uYx zyl#q)NsJ?~$c!84IP%o)L&H5WZ>*NF)E9eA7Hf_>?0ZS|j$G>6b>T-iKboPF?>d(J z74irJn1l1?MpaXTN`d=tKP3sGlB3BWjVtg>@nkhd^~r0Y+d>Ei)wxP z*L@?T(!Li>o5WcMn&gB6oXs((?M*1h2Nhd|KT=S#F=p3UD!tB$3j#@4y=N+W{ty+I ze@f+$8uB+qXwciWwZ-%B9ief4F$F)r!>pO=xxjJeGmIFNl)2Z~5D`$48}-S7OWiB2 ziobY?(CKrnMEsiT=k&SJoFh2P1|NeaQ@dYz^|>5pX4!mO76#?5v*}0bM{zIyngf3 za;sh?Ijto21*eZ##+Hl^A!Dw3u3g8)77|c}xHitP>VvV=mPil7FGZ(Q7i0WaRC=e6 zeGYxg63_|u0q%8~w`culKxsdf z?#UW1eboTg3rkphn!LzD(Ne?3bPagy_c(ob3N$vFx8HBRjhi`QY>BA z2Pv%mrg8-MnzKeui7QTDIde`vi03!U5KQluroNUO4VX!ELaobGPr%TUwu#+tn0|HI zv7>cvnbOBA>WP(4oR!Xug2kOn^bcZIhG0Abca>q-^=z^iDVfa6Eu=Ssi5){q_@An( z+VP+^OQtu)4E!7t)tj8ECz-D{#))*FuC1RGQ?g~%lvO<+*aI?(s3NZBfU^N_aX2Dm zFbUC|{S|fvaW=YgT1w`6u(ZQm4*8_)y7vyDHifB7ytTxW}6WADF&6X!;}EK z5jzV!8T+}YQB9Xp3PY2P%g|gjd{b^SCvHxj`Icx?kN>ZE~OLI$qv+O9}m9v`Me7xyPOBE^@^Ps)A%jNroU{t<=sS? zQCUp%nOPetz3nI9SgU*ayfq&RrhSq_2*1R0W2r_QH?Qt_7XhQR1 zibD#hCgID-nNTdg*G``(JzVw4DPlQe8IUe7xxP}}Z0H;ywLVd3pv?`$3KwhR7d*I^ zYLjKsAVQ-u^|FArWR8H?E|c`kO57UGRYIMiGs*n;vVlf!X=yGeII2E!d5%*7P3m}I zN`ADAxsDl|cp*LOqsG!U?nmJ#(b3c-I=ss+pXnxq9Wwy(cEYCq>K8+cG_M5njLKN7 zfvQu&*ke8gDwRPs@#Q>|pui|(7hA23Vsd1TTDu2?yn{q=Q1tz7P4fG@DjD1R-Zz^v z_tl6F8cbimwR28d3&>dfNct0;?Kb1!>E)Si4}N!__eaG0e`E|tKUaJ0b>&Olut8dL z&d&7M2$#4*?T#2x`G+C_8iC1#fkuccdj0ZD!zHiCd%1_!&IK8-EuuE~b*hMh?})#c z9%FiL0aOK@U9Q-R$tvs{&Dk}jb-tn=U)mLPLE79*UwZ|st-gbB;LZe^9in};)y+4m z1Of)p0&L<>cZQ_`i(CfXOZJy~m;9yIu`Z7VwT(V_x+7*2bm$iw03GGjA^yAmK)4{@ zM&dK2s>UtCL6wXnziSNCV{z-|^|N)7nW&(%W2o4LqQiin0A_<%aFcp0VfHc{EQdyH1d+immu_xszVG?>S&`a)lP#f zHrV0EsMO#;*dVxz&ugueX3Uj)-DX;g$%tb&#Nzs~n>E&A#RHp8|0eMPr)n%Rhk9vn zLH0m{a0BXI8z|Nmx5M4eSY~R!RWeXPfjc@tb-59p(w} zrbgk3>nT4j7?=lthw|MGi1i4rZmUug-+U|m>0;EflXo-VngXJoR?DV^0`DaGA1!xX zc7ceBH}EL!%FIqJC!Bu5z3WpCCR|*y6XW>t;Qk3uq0~0-6%6ew;T@V>=apsZ&GdJBCx5)nT0W8S-tG9*)Lb0-Bg_=Z_kVX#QWLdNgWIf5+JwPXaI6Moaup=bVAL{tsnn=i^OH{DiV!+YQOH6 zqQ3`Th3rJq1N$d={D034^w{|BzaJ~d06^YDq6JoW@N9R_BLl^Ze!L9&9{_S&|HJOY z*spt+AVJ@c4&Qg)wm*KN=vEiGIlYq~=w8u#k+2VpdyaQlEW^pT( zMh~&b>K=GC53bLfFRe=vFEWz<5YIlI*aBD?!r3=?_FBSMOdGE?<#FVF5XH~2I06C* zhoYtcfN~DA3dJO90qfoj1Qv_a1JEE|%*igP|9ct!xx1bzns=w4em)V|TnPcqD;gF( zAcXe5hvC3~=*9Ebqj)P@aBd`O{PpWkmU%VA>sEdVUEf6n>vYd5a<2(%5^((+Uu0nRev*lbVWR{__&j@w#I$SoeLn z*K?&9fI6%pLDZ4+Z$a~Brb!KP2Xp0V!Ewd0z*qQI|GSgt|8RsLSB(MC%=xbO1>P<) zt}UO0>T|nr-tLE2GzLq;o!anpdJia(Irh@VPHsPrB>rCSGr>c6z~A;r2yie2JH8@d zvfN6QS1!FL2j}7z7wAQdwF=fjtzc)QK;_zhUSSYYVS@36pK&cvTOgKN~2YWd&0pA;bS1x6X^*weUe#=+P zV6BqUP4ue=@XABc0)y?NABXAx+#((7!0Fwaao1D%rGwlT4Mmr@FzrN-)5YaM3vY^% zci}T!-mQNR@P|Jn)j%&(Q`7O}SNj|T^SlWl8@v0To6ZI{!OCH(Ni|A<0b{4ano*8k1D7I(MdqmX$k%9zy$yH4&z>LhTcL8M91t5iGzeDK% zfYS1VdFWgRMKZlL&f}*)&I|l~gj%|=AFdBw-Di7suMQIebBKKo7l7aPZzB}pgpxIn z@cWi&c|56omtR<=I#shB@6fn)UF&c14S^&SYJq`C83X!pRX`G3>e9}H|4)k3|K5Nt zcY%#Ad$9h6H``>Zz7N>`?P-aX>cGy|*`6v@JeN;EA&wiW;3;A9+xsqXm>EEI!n)O; z(3k(cT%0h-MtdmNk0gcP?TLT|bb9by(|%6iPJ0}g*b+YK3M{j_XoSj`)~=O2+Om-* zsjQnhmB8}|@7!|Xm-}MP)HX>Dt2!3{p$E{PqX^dl$4WtLsk%H60~aKTooWH3p?=_! zL8GsyL7m46Y_m#WOWbs(8gvj#fQsG=+}^dy+Fv04Gly;=E2Jzj?p}x$QmGBU9{ukD z{5T|SxeHW@%DUFp0K8ul0$CG!+=j$51=`#=BE#Dxg+pqdaS@K{f&WPkk3XxN&Fdh59z=+Ux>wRp#=|P!~|4)=S4bRz`B~Z1b=jEbHPx zM~U*r+I2UyDY*6oi29a|Y9oGiD$ON<%$I)DUWMy2#C$@gkul1W68m)BP=d26_9$E` z2|7mh0$NRI+7jTQm+MxmxRif_S7QAK zbq%L?)YnAC~~B9AA&d(rNfe=)(Gl*4MTcV5@qbuw>{%^#DAd zRi$Dk?Z;cn>OMW%J(n^z!mMvbEJ-YhEdY*|C(n1CT{PecmlhgSz#)pdLqy$Buk9}l z7^em}Qs=ii`nM{Pxa4>RqJUY(qLWDX3~-MF761!Qz9Il|clh!e*`gSiFb1v!0_jGNKE@=9z zO|Xn4e%Yfe`gF93$kgcn&KIy^)c*wJB5g65`Nz5{GPVox_E(h}BkN{QjKOZd)EA9# z?kN+{k`IwAZypzzL5!O9dPQ0NQi!XN7Xtl=OikT{9lXL^(sxV5{(}O$#{~m;yL8DXs&sfG$V1 zn&>l6JtMA{S$4-TchN7E*^d>tOUQt1#{k2^vMxMp`vTQ;a)9iGTK^5$$@3Sy18oJhgeNUy-5{VT z;CsGqRF*tF+U$jJb`U0XtJ-QcHfZlr&~j1K-H*WJQr!|-{kJpmkBj`DmSs~H41szk z1y&?~=nj9hCPLT+sS=&v`dMht%v=Q^@8N1%{ z0dO7@-D2Zf-70Mp^_d#o7pJrLh80=jAyO(+w!g%hYZM1%qXlz5ZAoUY%IfWee}46k zra}cIb~`*4$Yg87^`UD^gu(hNKuIkC5cIOi?GP%!o{AC*YK#!j?{clfxHP$wjPQr& zdjdK*2f#RX5kzLO{_=*uO6!Z3JUX<2gZiz(aFt_pixHlBR&J zK?o_+13HXO)-(ZzL~a_8lg!0&XVmUtSum{2Z{#_Ix^P42Yg29BnIuqj5v=BydS< zH`f?b!3#81VwPb*)_ibNhC**LD@l8zm(*h0()fYN&;1I zxGCQ>gT7@-+>zps4#D5{1^i&n2c#*Jvg?iHdu@XThF)|o@;Y`d>el~<9^uel<)jEE zB+xeFkz%R}!StJxVEt}4`cG56uJB=bZm9sBD zM&VF74pKx_TUY%mE(%`317btI&Pu=fAQ;a$8xWmLznR_rJeoSE_M*9@$Ux+O=(qgc z)*;D2SWy=t7sD{(=n=c3Qb#{xWL9q39Tsw2$YNRgt3StDRA}KPa>sUh7cLgV-3|gc zIh4#VR;!c<+-k@(DJyHRlpAucuX+WFwD-HQ?;6YZ%}dtCO8(-{c||~coNTp}tDKlH z_F69yQm1~aK$P6^4n#(6F6ZvNgR$-WtPuXUrPt4dB2%kl$gs)3fx?uQaB!iOQi?GI zW1-AL45T7%83lOu**FSBC>F*R9KUS5PyQ=sZ4;^@an8L2_?bGf>mk_o^D-i$*)+y< z`W9%aVNdYjT_smg4S^ZVPs`^gV2U-3ca!YiN`=)#zBgI3_yuvlPhs~tg)(0)>8 z%E^x)F13xEO&`8_uftp<7svWt`ANH|b|-}BF1uRDT>Fy;|Gi&ZzT$#2Loo$Yx1brBdxAyuW&N*2tU$Y>oFA`Y$2}4HY->)Fx=K8cNx(|Mwj}6e z5&6Yc3i2@^DhOGlh2z+I)ENB?0QK*qL>AS8^K41W81EMkhKy+-G;Fiow#k2*SZFCu zGaC=j#OA!ZDrHJX`SF4HCmJ#85P6)7qHZMEq}{UfK}z>AK)!*M6_|8fH z36nxxS|owIT*Gct?%2-O&ewspfv>82M|L&F#+nPXWH7KvHU8&+Y#A`l z3QR3q`?BG)W<9|C)qe(!F+p3YiQ^5&|NWr<_1r&y&5FPx=(EjEUCI5OhiA#S<546H z8Sl1LX#UT){Ncl8f`v(Vd9x#&;fXJQ{pMhB{Y>WOa_%$42)X||4?p;ZDR5B3h3k?{ zpZ;=MpqUd)d=O08?UB+DP#H-URssKQTPPPdUQuh@_eRVza!$_B*4H$;P7Q%hoJkeEnN* zMZ*Jz+wUq2(qG z&!G8(GK)^(JzVRO-};h%_W8&EPm2JqFvAlvUyR?~pWq5ZrhjYrsR8=u|HxXv23Od= z(K7b$U11gYDx-xgZ{v@75K1!-~EG=nBzRK!xO)4=sWXp^=CbM(sy9sje-|1gTwB#9$_P#fz<@M zoq&#kSHt)$I>La0nJ8#C$-*ZSYEFnLsDNd8A*^+dR9odDJk70#cT7k757|(hKR~s> zQ6jze(d|EvLa-NkX~yuH_j{!F_kS>-$1XG@XsDIffqtd9kI`EnEVBFf`@V<=o{Ns} z-s`j26xg9de6Ny#LZ1!a1qN(Of6$uK)#M6Ss0@MD71_{Z+z<2KJ)ts$Ap*?FmNBAF zak3qf{vK)*80yNil*51R1{rTK*18eb^zZXq55`*6o4y4D9}nKS2*%pF8}oK<^aEyi zm@HjK{dyDCEJ!3$q_tKuV(H~vp960^ zB)|hbg-xgx8L-_2YeH=Dp!1{g?7@OBmy)x#ItG$$dsGlIRrAuA2}>u+%N`g=^7Ip) z7NlO5Bo-%P`m>ZUnw@j~O1{JF_eltV-igP4ZG;R{XO@zQ0o%%d@8hFjnP+$`X}oIc z*EKpIMKKCvzWpz;op#DeZ^JI~QqY7xrIDq)8nC5#WN~n*DYg?NKdw?(g-XN4?ragr z57<5n5;hI{(;%FsHNbO^_u>L-DSLcaSO^AulX zHE?|R+wo+tgr~J$&369x(imbD=8eAmBK&=%_^g9eU@{tq+>D?z60Cc`@eCQ4i-VN=oTu-f=RRhEobKt4Y6Apv-}k(E32{D%S&&5LT@X>}=Cg1fa=ZmhYYy z$GC#!xFNrR!(gw%*G|fp6&f)4aGpSJXx4rD0#xEpyPi!WqFnPQSYB90Fv`a81- z;52MF>KZQ?SVM<+z{WkEe0YW+gb#P#oC=dWsXXiqv6R6n=;+jGv5}5Sy7C7dVeFqX z5B3SOxn!Oh>%X(h$d>f>!AaK(>3vLy?Tll>GM0PW|Ex954@%@bg9!omLK zb$nk+5f*)qLN+vuAcW_Q833IdG?;~_MaxZqQQGb2LnD6BlIqe#33Uy1%z>}z;L@eJ zH0{3yy+Dz;Dl@V|<}?hBCZNU5WWdV?Gth?HqSRWF*hVrpBV{oc{5e6IOkU!W#3_)V zs4Ba3lWSGNPk?6m@kNGDoAA+3Gq5!?AbD9M%N=`p&>LA)^HBP9AMp_+u?Of_ z+pJUU;H9A3YuIgF1116*n4r;8;x`U+ACbUg$vSBMWDvkwY&njWaM`B4(gUGq&pZIZ z%q?+JR(%9=dvcCvR@|Annm&Wh&{rVQW(r3>%dY48(aeMNSL33&qs^c4G#~NfSX_v6 z-JS97%pzi+YkNc-HIhMaMUWNePENocBM;h=k*Mj?sO-Bn9c|Uid6L)6zAv2&5^uD^ zSX~b&@$D}p=4A>6Xk~!@yW@9&yl#ozp`6pr8Nl|SeZrvZO3+hxi`=A=-nLA$lXltP z?s4xN%Nj;&af42QO?ifvy6SfqtOuJDQD4^pgswh<;QDDg0V`tVF)yeyd(M>h#6(FQ zu!8Q>*x&YRdzf#N^HdIAzXJs3!?V?NXSyyQlavMy@kn{=$^OX*($++`6~e8coI+Xh*$U4k(V|gXFIFDZ@kZDz+w0nMC|K{vo-M5 zWpy;`44gqxmh2{igqN>haOB-QKAbr};g#d2{Abcb%~lIW0`$w5rd-i1vQ%Gz$m+V~ zVIb&+T~_vt9LD_TZCA$;TCk!4PY*m;;nQ~qW%;6NgSJFzz~%Y~07t!%HzvKHPK1*B z*Th@yA8C1It{UW(XYkXy6hG1h_rfZjhOXLHoKr>IlEeh)tXOtAT_(XSaDOi*KhHq{ znSXYp)7{PGl57UZK|4sClTZ3uWV$E44Dq7ZBt-;n#GkGCnm-386L zo@EG2eZJ2finLRu|ZfzNx=xvAb{F#%g&`OXQ=2zeEzcpkn! z0~LY{cQ%qUQhgpmjVRlMCX7eg(V+Tt9%SUpZnSzwuN-0`$pdQK)v-yJRf3^RU+*6X zP{+RmsP*_jI>mxob008t@j9v9Rm`uH`qzOv@>@_gO{fxDn%HkF15*;QE#&=ubq_$M zLQ}f;;nKVFi9m6>a!VA_<%QZzmwRGS9YL}$pKJ*^8SUd9Ubu3z2lK*k5eNL{E(y9sA$>Am?-(MKrghOM!fiUK7yfylcDWUHDJ<0~2^bra7Xd1Gu74O7M|fLR5_^|-QjIJvU=o$05{_=&%1g4nkTu*cctv7s1Sy;eX>68) zwCVzRdk-a#pY4X?6LOBPen5O5e(O#TTOI*KeEWP<5>BhB!XOv^7>QTOs;r}x9CQL0 zWt>Jd@&{RWjhs%0)9v*iUm8goG{4?P5+RzeH8r!QKb+M1jEa45%aFM=RKhl=XZNIg zDR%9ArviiJJ-{2+2;|CGvXz$Y*sfxw>KDrR6C>BfN{sFYZ zt}pAerXsOA1XW5BGjk1E6W^8Gk6?F|;O;0KZbG`r4>hyptaz;6=jod%OlzA$x$kc* zgi_$L^rg;yiX9-P+aq%719b5Is2G5~7Gw=Wkr~>)crPPou4r%os$3+9*PO4yP(HG^ zB*PLJ&n9n_W$-`$L<>dywhc07J)1`c-@XEyWrMR=%%d#f;0#R7N1z>QddnOWQCu~O zcfUIC>x(09IM^pl8}bK0_LDS9^8-9@ZdCGBUL4b^Ah=ju%)9pdd(K6cr|q2WIR*z# zy4j$8m;W55)-C{kIEt?_H3LP8w_sw8hZduS=_tayh(jcvt~Bn>o(G&?7Y;@fGONEMll z{xPINs*x3n^>pFF0!8{)GzQ_ljZ`AyICU(CjYQ(7eX-lylv_aEoDgcxop5=9#%Xo` zjNFNIGtha|iF0~gSg!OQplCk_#>oR_qI$wPD>B(w2$TWZ?xZ6qwt2NJi6tcCe9uh_NezPN;zb1Y4rfk~)0C4DE#Or-V6(Wj$Vrzg&to2De zeh8AVvC~XM$X^W*6vhN1vaTj_i2ER9vk1MFnC^D|{$wSn;64JyVOWNr^X=v)Eiw2V z0v>h*mM$sBbo<#-+8a9zcT=wEtuWm{Tpb@0sL6Zhwe|L6lsjGX*5pW2u^tqq*>VTV z#UaPgh1F?nC0UkOte^tYIRiyFsOSVd|D*;-aqMLhzg_edVh^Kg@VwEZLCh3;W^(| z*o`p&UnsZR)ep3hJ~{luPX{G+xdydTvmKWaW(?&gM%A-SPuB-BHLE0EbCu|3J_57Z3&0Imt!G;1S-DIQTZjz{4M^e6M);cBlm6gU|GQY}D?Zu?ZSL?%o4^5p=^dZi#mT z#&iOsH<-7rzeQR9G6Dv(e}YE006l&~&om zMC7DB;JQSJ7{JpNnFYIfL02wm1T= zVwyapOUCkab7ei!!k1ej99i9 zWwPiNJo$#^W7PAF=~+YP`}|*ieM$WZ8-wSXz2esjknzBQvt8qJQYw1ljzp9VyFyD# z8_GXiO&v?8<@*Ni0cL%oLHZD4(LHO6&pW0%?v^ae7`Ca#RLAkgSv|oyx2VPR zEfwENif z;OUA2hq`U3KoSUfEJ1;P7Kh2iZ^Ul9Z) z*+%Mr=|9gNX`-UiTMe$Aj%`RIsa=^kG6)Lz04`EmZ3*g_bNg#X$s_Xs(DB-nl073| zKZ)p}(>ZzrbxK^Ts26{RlV&~c18YETYK4TLr9zD1I-kFoEMa)Dj6vcsXxKoO*$i57 zwN|nQdg-y48_5ml5Xtx?635MprL(ELdZt4)ll@7CNg8aU(0f3~TsK@tF}Y`7AjO)` z34l%}t4IB8FLklN_s#40(DQYy^(CN@jN_$@8nb}yF>VHLYpU?W5QesTZ5y`K@v!+w zp>cf%+EC3(H1n6Xzz^r(YM=GP)y7S-II^9|O5#`4A=ZW{2_=%!S}KSK1ZyAko7wza z6PgnYn~j=Z-R?m_36Bf%9dt6q`Z!Ef8U1$J>cRdVVOg9e;0r7G!<{Hy^CvT8wM!W$ zWL~Odn`GY9xD%97%eC&f8+H2pDC&C%90i5I3Zt}CgL}LHklaDewel1_lFWN>HiBsX zChxh+mF4evm!Zyb<+w&y6m02U$~#I8DO|T8Zl!6*A$;g(1mra<`h6>3m<_Wczk~ zL40msZERCvTIf@3$1_qL*%QH&jn49|#*3Xoz$b?imJ~5*&4ya8ru(0y`mQ1>Y2Cw9 zd(+=N3kTd2kwEp+7Y%vF>YD`!;M;!_;gLoYYC&SU1W9T31^J&|QSR3|X`5nMUgnZ=bKvqA;zgOBTvQ;S-B4lYTO1-UpBa>vPy+j z7PDumqp?bA)!80Tm+JqCI?0Qj#o%y&FftbuMdE3l4xizZV-V?(YAb?iw9&z|^fEHm{gXZSKkc3;(OIm+#L z?)$}ri?}#5QTFI#kITP;nC%N$uFaw-eJQgZnk%Q;E!ap~(VnQVR{krN=;_jMoP5yk zPn6gGrc&IU{g1VN>?5nLoU3^4nvJ*@Ozd*whwa+cdpvx1w1{8Kw)sHlTvwXy2>(yF zF7dddp9Ca{h+a)3oFyid$#=36LJydFj+&9CTTGdx%{p?u@3AwetJ2+i*MduO_%N&C$B-AGM6%} zE11{-(S9KN~*yOQ_@4>5qwtT;o!Zlc4B*Qd}{lFI=w z>9uDY-3oH=v_14RxD|@YBZCg695a1?0qd=CktEYjUZ;{Irobywb^dNfKHd;Iyerpn zFMMq1C7GUJ_}I7Yrh?A7_R7>};`348_X0+18( zyGfI|*%{;*x=+KcC~rL5%`;v@l_iDbCcW`8g_Djv{R|iS$dk$X=-C4I;gT8XPEOeo zpHaI{epjVe_=2Ram^_kgMQsmSB9BNJ^#!qoG0>w-5C6ItV#|{4+t2Qi{Jn;%B>hv#nok#ywuo%F0Zg+sYDOR`c}*7y26BnEQH znrCTyQQylyS2zYApL*-{#?mI|i&)jM%O$?#WO)JCK z2d2iI%w^{yyrd9EyXF4LuKIx%FwdMx)0i0)j0x=$g>VBD@oIl4xPt&K)Z9R!YotJ zcF_GXIp-0dpx9-{;>4Q_s*>8iEA8hwBis3Ebq8KFM{sOQsQ+0?x0MP87atHbT1YsS zDmxOiViPd+C+%%0Pb6_P6XwUU@+->M>?{y&#O>^zu(MU6zntfsUoFj`t0A{EqeWX~ z>TXt3kuj_%whQ@;DSBDa3u1cxz43&3n)$mAd!I!$-)NShN7Cg z;QEpbMH3jOfo&6pFI^ycNG|*umTOaGv-asE!TZ+5pznbR>&8`J;9y_PFfVr;U6|2< zzw%g216)1ka{AUWfFKQufKt*bB_SY6DJ7*KA+;0< z1EfJfQo0)iq#LA>=DluUpEKTPjQ`m_AKp*z`QqLTmy5OLJ?FgQ7njf3$W0~*Bw2<= zP}9d6!P>&@Sy+v>b%EUpEIcR14Aw-;wuaNrnHfKqSGM=(R)*YeNRT!o_a+p-ZV}TF zw=pq!hqNVE?kas9bRT1>LqfA<@b={R{FBE#9?=(roV`ohNz}xN8ZG9|LLGj++KS{u zCe%N^T@^lWl&w4J%7gAnOu;-e0_F7A@?7Jzh4Mdd-dCZq&eag+P)Ofhx47~T_%kDj z*3fqEJ%Isq2?Jk_+j?WJSu*yhwy#U3`xH!140*wSou>C2b z{k58bAAo<9rIGrhlgzd0qTeLH9zxqc`F@S(JW_OU&%eay22@u)PAViyq?$ss$wbL4 zm-0tNrXAv~8rbg(!VS)c(5&fasGt#)?PhLgl##|sGPV1?o-tZfU#7D?;Zl7=kZgF& z@bR=hZ7I?rgg92STl7s&%FX<@LIb#L~Sk1z{TFXe-NWcqP;}UEssCj(}}|@ znNnEhq>W$T@n~jDlPoq>ua1uTG5mgXX8Y;=?O($dHa~HY=>K(c`0cLB_b8x%4ITYSg|O)T+x+rt+QHZD>qK z@^gIyI}H!2IwFBIW*xPUTdA{Wk+f$VlhQbAtLXrcqaTmnN@JFi#3^`6^F5uuGR)3B zw`Ln+4_$9Re4Y~C<&t0ivZ(>?Oj{QuY$=}L9>TP&m&~S+esDeCd@g_SrvOey9cv5z z6K_^$X!?{}zV9Y>$^gAl!9AfxjD_p!%}AGio)_~m*s==rI|t3c?d)Jyw!2pC9Ck4C z4Ja~>Z_6`{tBQiuU!b)nKIJO ze9fl~wu8YfJNRy`zHUk0n_yZdO6xM>MK)jvT05_@Y*~y-#9Tf5)>A!s9z*I|E~7FS zZD2@7L~~WACgM_c1Gg8MRTlxZJf-;?d4Bm)#K zX3XBIA@I$}S5A##T^JfV#<4kUhV9^J#g%CHBrzeHxhAM)j$wh+F(`KQoeC=}Y zYGvWa8=7O=ZqVlsX#FkTc$+3a-uby3OTzHC--wi@S2yKUc(&U)Ys~Ak_OjjLR#ESM zYlfQczo!qJf_^|eQ+duXw33Js%Ovh7W!=8cu3LLjiB>-pH(aoj!;Knbt)UK zL>I4YP;W#{y&2AkT*BnzEu#%SH9I)tR0MtrK(!Hl7s)x64={#GzK6<{?o1-GpXaan z{_w>0RuYwJiKEVfq6cpsKGwCvh#DL)*$_@dtS7LQC7~#gz|;Cg2WHUrMZtTnZ-hwSm}!Ta5Jk(x zyfq+xL&wUM)btBrvU}eWT0md5Z^OAetKX°S$fL*tf)R)^V7b$W+(vY)!?nM)+I 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

-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 literal 0 HcmV?d00001 diff --git a/doc/cluster/aws/create_efs.png b/doc/cluster/aws/create_efs.png new file mode 100644 index 0000000000000000000000000000000000000000..f4d448d1518e11a11d535efb9c3a78b56cc13149 GIT binary patch literal 250321 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 *jO-vF; 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

ch*lh^Fd-L{wtn70(4-NC^j=dZ7rY!4#Bo|jWf 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

*j9=5&^KeazLa(8G(AWOc& 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/cluster/config-default.sh` to change this behavior as follows: -This process takes about 5 to 10 minutes. +``` +export KUBE_AWS_ZONE=us-west-2a +export NUM_NODES=3 +export MASTER_SIZE=m3.medium +export NODE_SIZE=m3.large +export AWS_S3_REGION=us-west-2a +export AWS_S3_BUCKET=mycompany-kubernetes-artifacts +export KUBE_AWS_INSTANCE_PREFIX=k8s +... -Once the cluster is up, the IP addresses of your master and node(s) will be printed, as well as information about the default services running in the cluster (monitoring, logging, dns). +``` -User credentials and security tokens are written in `~/.kube/config`, they will be necessary to use the CLI or the HTTP Basic Auth. +This process takes about 5 to 10 minutes. ``` -[ec2-user@ip-172-31-24-50 ~]$ export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash +[ec2-user@ip-172-31-27-229 ~]$ export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash 'kubernetes' directory already exist. Should we skip download step and start to create cluster based on it? [Y]/n Skipping download step. Creating a kubernetes on aws... @@ -65,74 +77,72 @@ Creating a kubernetes on aws... ... calling kube-up Starting cluster using os distro: jessie Uploading to Amazon S3 -+++ Staging server tars to S3 Storage: kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel -upload: ../../../tmp/kubernetes.7nMCAR/s3/bootstrap-script to s3://kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/bootstrap-script ++++ Staging server tars to S3 Storage: kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel +upload: ../../../tmp/kubernetes.KsacFg/s3/bootstrap-script to s3://kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel/bootstrap-script Uploaded server tars: - SERVER_BINARY_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/kubernetes-server-linux-amd64.tar.gz - SALT_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/kubernetes-salt.tar.gz - BOOTSTRAP_SCRIPT_URL: https://s3.amazonaws.com/kubernetes-staging-98b0b8ae5c8ea0e33a0faa67722948f1/devel/bootstrap-script -INSTANCEPROFILE arn:aws:iam::525016323257:instance-profile/kubernetes-master 2016-11-22T05:20:41Z AIPAJWBAGNSEHM4CILHDY kubernetes-master / -ROLES arn:aws:iam::525016323257:role/kubernetes-master 2016-11-22T05:20:39Z / AROAJW3VKVVQ5MZSTTJ5O kubernetes-master + SERVER_BINARY_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel/kubernetes-server-linux-amd64.tar.gz + SALT_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel/kubernetes-salt.tar.gz + BOOTSTRAP_SCRIPT_URL: https://s3.amazonaws.com/kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel/bootstrap-script +INSTANCEPROFILE arn:aws:iam::330323714104:instance-profile/kubernetes-master 2016-12-01T03:19:54Z AIPAIQDDLSMLWJ2QDXM6I kubernetes-master / +ROLES arn:aws:iam::330323714104:role/kubernetes-master 2016-12-01T03:19:52Z / AROAJDKKDIYHJTTEJM73M kubernetes-master ASSUMEROLEPOLICYDOCUMENT 2012-10-17 STATEMENT sts:AssumeRole Allow PRINCIPAL ec2.amazonaws.com -INSTANCEPROFILE arn:aws:iam::525016323257:instance-profile/kubernetes-minion 2016-11-22T05:20:45Z AIPAIYVABOPWQZZX5EN5W kubernetes-minion / -ROLES arn:aws:iam::525016323257:role/kubernetes-minion 2016-11-22T05:20:43Z / AROAJKDVM7XQNZ4JGVKNO kubernetes-minion +INSTANCEPROFILE arn:aws:iam::330323714104:instance-profile/kubernetes-minion 2016-12-01T03:19:57Z AIPAJGNG4GYTNVP3UQU4S kubernetes-minion / +ROLES arn:aws:iam::330323714104:role/kubernetes-minion 2016-12-01T03:19:55Z / AROAIZVAWWBIVUENE5XB4 kubernetes-minion ASSUMEROLEPOLICYDOCUMENT 2012-10-17 STATEMENT sts:AssumeRole Allow PRINCIPAL ec2.amazonaws.com -Using SSH key with (AWS) fingerprint: 08:9f:6b:82:3d:b5:ba:a0:f3:db:ab:94:1b:a7:a4:c7 +Using SSH key with (AWS) fingerprint: 70:66:c6:3d:53:3b:e5:3d:1d:7f:cd:c9:d1:87:35:81 Creating vpc. -Adding tag to vpc-fad1139d: Name=kubernetes-vpc -Adding tag to vpc-fad1139d: KubernetesCluster=kubernetes -Using VPC vpc-fad1139d -Adding tag to dopt-e43a7180: Name=kubernetes-dhcp-option-set -Adding tag to dopt-e43a7180: KubernetesCluster=kubernetes -Using DHCP option set dopt-e43a7180 +Adding tag to vpc-e01fc087: Name=kubernetes-vpc +Adding tag to vpc-e01fc087: KubernetesCluster=kubernetes +Using VPC vpc-e01fc087 +Adding tag to dopt-807151e4: Name=kubernetes-dhcp-option-set +Adding tag to dopt-807151e4: KubernetesCluster=kubernetes +Using DHCP option set dopt-807151e4 Creating subnet. -Adding tag to subnet-fc16fa9b: KubernetesCluster=kubernetes -Using subnet subnet-fc16fa9b +Adding tag to subnet-4a9a642d: KubernetesCluster=kubernetes +Using subnet subnet-4a9a642d Creating Internet Gateway. -Using Internet Gateway igw-fc0d9398 +Using Internet Gateway igw-821a73e6 Associating route table. Creating route table -Adding tag to rtb-bd8512da: KubernetesCluster=kubernetes -Associating route table rtb-bd8512da to subnet subnet-fc16fa9b -Adding route to route table rtb-bd8512da -Using Route Table rtb-bd8512da +Adding tag to rtb-0d96fa6a: KubernetesCluster=kubernetes +Associating route table rtb-0d96fa6a to subnet subnet-4a9a642d +Adding route to route table rtb-0d96fa6a +Using Route Table rtb-0d96fa6a Creating master security group. Creating security group kubernetes-master-kubernetes. -Adding tag to sg-d9280ba0: KubernetesCluster=kubernetes +Adding tag to sg-a47564dd: KubernetesCluster=kubernetes Creating minion security group. Creating security group kubernetes-minion-kubernetes. -Adding tag to sg-dc280ba5: KubernetesCluster=kubernetes -Using master security group: kubernetes-master-kubernetes sg-d9280ba0 -Using minion security group: kubernetes-minion-kubernetes sg-dc280ba5 +Adding tag to sg-9a7564e3: KubernetesCluster=kubernetes +Using master security group: kubernetes-master-kubernetes sg-a47564dd +Using minion security group: kubernetes-minion-kubernetes sg-9a7564e3 Creating master disk: size 20GB, type gp2 -Adding tag to vol-04d71a810478dec0d: Name=kubernetes-master-pd -Adding tag to vol-04d71a810478dec0d: KubernetesCluster=kubernetes -Allocated Elastic IP for master: 35.162.175.115 -Adding tag to vol-04d71a810478dec0d: kubernetes.io/master-ip=35.162.175.115 -Generating certs for alternate-names: IP:35.162.175.115,IP:172.20.0.9,IP:10.0.0.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,DNS:kubernetes-master +Adding tag to vol-0eba023cc1874c790: Name=kubernetes-master-pd +Adding tag to vol-0eba023cc1874c790: KubernetesCluster=kubernetes +Allocated Elastic IP for master: 35.165.155.60 +Adding tag to vol-0eba023cc1874c790: kubernetes.io/master-ip=35.165.155.60 +Generating certs for alternate-names: IP:35.165.155.60,IP:172.20.0.9,IP:10.0.0.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,DNS:kubernetes-master Starting Master -Adding tag to i-042488375c2ca1e3e: Name=kubernetes-master -Adding tag to i-042488375c2ca1e3e: Role=kubernetes-master -Adding tag to i-042488375c2ca1e3e: KubernetesCluster=kubernetes +Adding tag to i-097f358631739e01c: Name=kubernetes-master +Adding tag to i-097f358631739e01c: Role=kubernetes-master +Adding tag to i-097f358631739e01c: KubernetesCluster=kubernetes Waiting for master to be ready -Attempt 1 to check for master nodeWaiting for instance i-042488375c2ca1e3e to be running (currently pending) -Sleeping for 3 seconds... -Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Attempt 1 to check for master nodeWaiting for instance i-097f358631739e01c to be running (currently pending) Sleeping for 3 seconds... -Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Waiting for instance i-097f358631739e01c to be running (currently pending) Sleeping for 3 seconds... -Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Waiting for instance i-097f358631739e01c to be running (currently pending) Sleeping for 3 seconds... -Waiting for instance i-042488375c2ca1e3e to be running (currently pending) +Waiting for instance i-097f358631739e01c to be running (currently pending) Sleeping for 3 seconds... [master running] -Attaching IP 35.162.175.115 to instance i-042488375c2ca1e3e -Attaching persistent data volume (vol-04d71a810478dec0d) to master -2016-11-23T02:14:59.645Z /dev/sdb i-042488375c2ca1e3e attaching vol-04d71a810478dec0d +Attaching IP 35.165.155.60 to instance i-097f358631739e01c +Attaching persistent data volume (vol-0eba023cc1874c790) to master +2016-12-13T10:56:50.378Z /dev/sdb i-097f358631739e01c attaching vol-0eba023cc1874c790 cluster "aws_kubernetes" set. user "aws_kubernetes" set. context "aws_kubernetes" set. @@ -145,32 +155,34 @@ Creating autoscaling group 0 minions started; waiting 0 minions started; waiting 0 minions started; waiting - 2 minions started; ready + 0 minions started; waiting + 3 minions started; ready Waiting for cluster initialization. This will continually check to see if the API for kubernetes is reachable. This might loop forever if there was some uncaught error during start up. -.......................................................................................................................................................................................................................Kubernetes cluster created. +...........................................................................................................................................................................Kubernetes cluster created. Sanity checking cluster... -Attempt 1 to check Docker on node @ 35.164.79.249 ...working -Attempt 1 to check Docker on node @ 35.164.83.190 ...working +Attempt 1 to check Docker on node @ 35.165.35.181 ...working +Attempt 1 to check Docker on node @ 35.165.79.208 ...working +Attempt 1 to check Docker on node @ 35.163.90.67 ...working Kubernetes cluster is running. The master is running at: - https://35.162.175.115 + https://35.165.155.60 The user name and password to use is located in /home/ec2-user/.kube/config. ... calling validate-cluster -Waiting for 2 ready nodes. 0 ready nodes, 2 registered. Retrying. -Waiting for 2 ready nodes. 1 ready nodes, 2 registered. Retrying. -Waiting for 2 ready nodes. 1 ready nodes, 2 registered. Retrying. -Found 2 node(s). -NAME STATUS AGE -ip-172-20-0-23.us-west-2.compute.internal Ready 54s -ip-172-20-0-24.us-west-2.compute.internal Ready 52s +Waiting for 3 ready nodes. 0 ready nodes, 3 registered. Retrying. +Waiting for 3 ready nodes. 0 ready nodes, 3 registered. Retrying. +Found 3 node(s). +NAME STATUS AGE +ip-172-20-0-186.us-west-2.compute.internal Ready 33s +ip-172-20-0-187.us-west-2.compute.internal Ready 34s +ip-172-20-0-188.us-west-2.compute.internal Ready 34s Validate output: NAME STATUS MESSAGE ERROR scheduler Healthy ok @@ -180,14 +192,14 @@ etcd-0 Healthy {"health": "true"} Cluster validation succeeded Done, listing cluster services: -Kubernetes master is running at https://35.162.175.115 -Elasticsearch is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/elasticsearch-logging -Heapster is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/heapster -Kibana is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kibana-logging -KubeDNS is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kube-dns -kubernetes-dashboard is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard -Grafana is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana -InfluxDB is running at https://35.162.175.115/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb +Kubernetes master is running at https://35.165.155.60 +Elasticsearch is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/elasticsearch-logging +Heapster is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/heapster +Kibana is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/kibana-logging +KubeDNS is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/kube-dns +kubernetes-dashboard is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard +Grafana is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana +InfluxDB is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. @@ -197,31 +209,36 @@ Installation successful! ``` -By default, the script will provision a new VPC and a 4 node k8s cluster in us-west-2a (Oregon) with EC2 instances running on Debian. You can override the variables defined in `/cluster/config-default.sh` to change this behavior as follows: +Once the cluster is up, the IP addresses of your master and node(s) will be printed, as well as information about the default services running in the cluster (monitoring, logging, dns). + +User credentials and security tokens are written in `~/.kube/config`, they will be necessary to use the CLI or the HTTP Basic Auth. + -``` -export KUBE_AWS_ZONE=us-west-2a -export NUM_NODES=2 -export MASTER_SIZE=m3.medium -export NODE_SIZE=m3.large -export AWS_S3_REGION=us-west-2a -export AWS_S3_BUCKET=mycompany-kubernetes-artifacts -export KUBE_AWS_INSTANCE_PREFIX=k8s -... -``` And then concate the kubernetes binaries directory into PATH: + ``` export PATH=/platforms/linux/amd64:$PATH ``` -Now you can use administration tool kubectl to operate the cluster. -By default, kubectl will use the kubeconfig file generated during the cluster startup for authenticating against the API, the location is in `~/.kube/config`. + + +Now you can use administration tool `kubectl` to operate the cluster. +By default, `kubectl` will use the kubeconfig file generated during the cluster startup for authenticating against the API, the location is in `~/.kube/config`. + ###Setup PaddlePaddle Environment on AWS -For the design of running PaddlePaddle on Kubernetes, you really need to read [this article](https://github.com/drinktee/Paddle/blob/k8s/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md) first. +Now, we've created a cluster with following network capability: + +1. All Kubernetes nodes can communicate with each other. + +1. All Docker containers on Kubernetes nodes can communicate with each other. + +1. All Kubernetes nodes can communicate with all Docker containers on Kubernetes nodes. + +1. All other traffic loads from outside of Kubernetes nodes cannot reach to the Docker containers on Kubernetes nodes except for creating the services for containers. For sharing the training data across all the Kubernetes nodes, we use EFS (Elastic File System) in AWS. Ceph might be a better solution, but it requires high version of Linux kernel that might not be stable enough at this moment. We haven't automated the EFS setup at this moment, so please do the following steps: @@ -229,23 +246,182 @@ For sharing the training data across all the Kubernetes nodes, we use EFS (Elast 1. Make sure you add the AmazonElasticFileSystemFullAccess policy into your AWS account. -2. Create the Elastic File System in AWS console, and attach the Kubernetes VPC with it. +1. Create the Elastic File System in AWS console, and attach the Kubernetes VPC with it. +![create_efs](create_efs.png =800x) + +1. Modify the Kubernetes security group under ec2/Security Groups, add additional inbound policy "All TCP TCP 0 - 65535 0.0.0.0/0" for Kubernetes default VPC security group. +![add_security_group](add_security_group.png =800x) + + +1. Follow the EC2 mount instruction to mount the disk onto all the Kubernetes nodes, we recommend to mount EFS disk onto ~/efs. +![efs_mount](efs_mount.png =800x) + + +Before starting the training, you should place your user config and divided training data onto EFS. When the training start, each task will copy related files from EFS into container, and it will also write the training results back onto EFS, we will show you how to place the data later in this article. + + + +###Core Concept of PaddlePaddle Training on AWS + +Now we've already setup a 3 node distributed training cluster, and on each node we've attached the EFS volume, in this training demo, we will create three Kubernetes pod and scheduling them on 3 node. Each pod contains a PaddlePaddle container. When container gets created, it will start pserver and trainer process, load the training data from EFS volume and start the distributed training task. + +####Use Kubernetes Job + +We use Kubernetes job to represent one time of distributed training. After the job get finished, Kubernetes will destroy job container and release all related resources. -3. Modify the Kubernetes security group, add additional inbound policy "All TCP TCP 0 - 65535 0.0.0.0/0" for Kubernetes default VPC security group. +We can write a yaml file to describe the Kubernetes job. The file contains lots of configuration information, for example PaddlePaddle's node number, `paddle pserver` open port number, the network card info etc., these information are passed into container for processes to use as environment variables. -4. Follow the EC2 mount instruction to mount the disk onto all the Kubernetes nodes, we recommend to mount EFS disk onto ~/efs. +In one time of distributed training, user will confirm the PaddlePaddle node number first. And then upload the pre-divided training data and configuration file onth EFS volume. And then create the Kubernetes job yaml file; submit to the Kubernetes cluster to start the training job. +####Create PaddlePaddle Node + +After Kubernetes master gets the request, it will parse the yaml file and create several pods (PaddlePaddle's node number), Kubernetes will allocate these pods onto cluster's node. A pod represents a PaddlePaddle node, when pod is successfully allocated onto one physical/virtual machine, Kubernetes will startup the container in the pod, and this container will use the environment variables in yaml file and start up `paddle pserver` and `paddle trainer` processes. + + +####Start up Training + +After container gets started, it starts up the distributed training by using scripts. We know `paddle train` process need to know other node's ip address and it's own trainer_id, since PaddlePaddle currently don't have the ability to do the service discovery, so in the start up script, each node will use job pod's name to query all to pod info from Kubernetes apiserver (apiserver's endpoint is an environment variable in container by default). + +With pod information, we can assign each pod a unique trainer_id. Here we sort all the pods by pod's ip, and assign the index to each PaddlePaddle node as it's trainer_id. The workflow of starting up the script is as follows: + +1. Query the api server to get pod information, and assign the trainer_id by sorting the ip. +1. Copy the training data from EFS sharing volume into container. +1. Parse the `paddle pserver` and 'paddle trainer' startup parameters from environment variables, and then start up the processes. +1. PaddlePaddle will automatically write the result onto the PaddlePaddle node with trainer_id:0, we set the output path to be the EFS volume to save the result data. -And now you can place your training data onto the EFS, you should follow [this article](https://github.com/drinktee/Paddle/blob/k8s/doc_cn/cluster/k8s/distributed_training_on_kubernetes.md) to locate your data. ###Start PaddlePaddle Training Demo on AWS -After setting up all the steps on AWS, We can start up our PaddlePaddle training recommendation demo by using: +Now we'll start a PaddlePaddle training demo on AWS, steps are as follows: + +1. Build PaddlePaddle Docker image. +1. Divide the training data file and upload it onto the EFS sharing volume. +1. Create the training job yaml file, and start up the job. +1. Check the result after training. + +####Build PaddlePaddle Docker Image + +PaddlePaddle docker image need to provide the runtime environment for `paddle pserver` and `paddle train`, so the container use this image should have two main function: + +1. Copy the training data into container. +1. Generate the startup parameter for `paddle pserver` and `paddle train` process, and startup the training. + + +Since official `paddledev/paddle:cpu-latest` have already included the PaddlePaddle binary, but lack of the above functionalities, so we will create the startup script based on this image, to achieve the work above. the detailed Dockerfile is as follows: ``` -kubectl create -f job.yaml +FROM paddledev/paddle:cpu-latest + +MAINTAINER zjsxzong89@gmail.com + +COPY start.sh /root/ +COPY start_paddle.py /root/ +CMD ["bash"," -c","/root/start.sh"] ``` +At this point, we will copy our `start.sh` and `start_paddle.py` file into container, and then exec `start_paddle.py` script to start up the training, all the steps like assigning trainer_id, getting other nodes' ip are implemented in `start_paddle.py`. + +`start_paddle.py` will start parsing the parameters. + +``` +parser = argparse.ArgumentParser(prog="start_paddle.py", + description='simple tool for k8s') + args, train_args_list = parser.parse_known_args() + train_args = refine_unknown_args(train_args_list) + train_args_dict = dict(zip(train_args[:-1:2], train_args[1::2])) + podlist = getPodList() +``` + +And then using function `getPodList()` to query all the pod information from the job name through Kubernetes api server. When all the pods are in the running status, using `getIdMap(podlist)` to get the trainer_id. + +``` + podlist = getPodList() + # need to wait until all pods are running + while not isPodAllRunning(podlist): + time.sleep(10) + podlist = getPodList() + idMap = getIdMap(podlist) +``` + +In function `getIdMap(podlist)`, we use podlist to get the ip address for each pod and sort them, use the index as the trainer_id. + +``` +def getIdMap(podlist): + ''' + generate tainer_id by ip + ''' + ips = [] + for pod in podlist["items"]: + ips.append(pod["status"]["podIP"]) + ips.sort() + idMap = {} + for i in range(len(ips)): + idMap[ips[i]] = i + return idMap +``` + +After getting `idMap`, we use function `startPaddle(idMap, train_args_dict)` to generate `paddle pserver` and `paddle train` start up parameters and then start up the processes. + +In function `startPaddle`, the most important work is to generate `paddle pserver` and `paddle train` start up parameters. For example, `paddle train` parameter parsing, we will get parameters like `PADDLE_NIC`, `PADDLE_PORT`, `PADDLE_PORTS_NUM`, and get the `trainer_id` from `idMap`. + +``` + program = 'paddle train' + args = " --nics=" + PADDLE_NIC + args += " --port=" + str(PADDLE_PORT) + args += " --ports_num=" + str(PADDLE_PORTS_NUM) + args += " --comment=" + "paddle_process_by_paddle" + ip_string = "" + for ip in idMap.keys(): + ip_string += (ip + ",") + ip_string = ip_string.rstrip(",") + args += " --pservers=" + ip_string + args_ext = "" + for key, value in train_args_dict.items(): + args_ext += (' --' + key + '=' + value) + localIP = socket.gethostbyname(socket.gethostname()) + trainerId = idMap[localIP] + args += " " + args_ext + " --trainer_id=" + \ + str(trainerId) + " --save_dir=" + JOB_PATH_OUTPUT +``` + +Use `docker build` to build toe Docker Image: + +``` +docker build -t your_repo/paddle:mypaddle . +``` + +And then push the built image onto docker registry. + +``` +docker push your_repo/paddle:mypaddle +``` + +####Upload Training Data File + +Here we will use PaddlePaddle's official recommendation demo as the content for this training, we put the training data file into a directory named by job name, which located in EFS sharing volume, the tree structure for the directory looks like: + +``` +efs +└── paddle-cluster-job + ├── data + │ ├── 0 + │ │ + │ ├── 1 + │ │ + │ └── 2 + ├── output + └── recommendation +``` + +The `paddle-cluster-job` directory is the job name for this training, this training includes 3 PaddlePaddle node, we store the pre-divided data under `paddle-cluster-job/data` directory, directory 0, 1, 2 each represent 3 nodes' trainer_id. the training data in in recommendation directory, the training results and logs will be in the output directory. + + +####Create Kubernetes Job + +Kubernetes use yaml file to describe job details, and then use command line tool to create the job in Kubernetes cluster. + +In yaml file, we describe the Docker image we use for this training, the node number we need to startup, the volume mounting information and all the necessary parameters we need for `paddle pserver` and `paddle train` processes. + The yaml file content is as follows: ``` @@ -297,14 +473,93 @@ spec: restartPolicy: Never ``` -It will generate three PaddlePaddle job runing on distributed Kubernetes nodes, and all the training result will be written into EFS. -We've made an experiment of running this PaddlePaddle recommendation training demo on three 2 core 8 GB machine (m3.large), and it took 8 hours to generate 10 models. +In yaml file, the metadata's name is the job's name. `parallelism, completions` means this job will simultaneously start up 3 PaddlePaddle nodes, and this job will be finished when there are 3 finished pods. For the data store volume, we declare the path jobpath, it mount the /home/admin/efs on host machine into the container with path /home/jobpath. So in container, the /home/jobpath actually stores the data onto EFS sharing volume. + +`env` field represents container's environment variables, we pass the PaddlePaddle parameters into containers by using the `env` field. + +`JOB_PATH` represents the sharing volume path, `JOB_NAME` represents job name, `TRAIN_CONFIG_DIR` represents the training data file directory, we can these three parameters to get the file path for this training. + +`CONF_PADDLE_NIC` represents `paddle pserver` process's `--nics` parameters, the NIC name. +`CONF_PADDLE_PORT` represents `paddle pserver` process's `--port` parameters, `CONF_PADDLE_PORTS_NUM` represents `--port_num` parameter. + +`CONF_PADDLE_PORTS_NUM_SPARSE` represents the sparse updated port number, `--ports_num_for_sparse` parameter. + +`CONF_PADDLE_GRADIENT_NUM` represents the training node number, `--num_gradient_servers` parameter. + +After we create the yaml file, we can use Kubernetes command line tool to create the job onto the cluster. + +``` +kubectl create -f job.yaml +``` + +After we execute the above command, Kubernetes will create 3 pods and then pull the PaddlePaddle image, then start up the containers for training. + + + +####Check Training Results + +During the training, we can see the logs and models on EFS sharing volume, the output directory contains the training results. (Caution: node_0, node_1, node_2 directories represents PaddlePaddle node and train_id, not the Kubernetes node) + +``` +[root@paddle-kubernetes-node0 output]# tree -d +. +├── node_0 +│ ├── server.log +│ └── train.log +├── node_1 +│ ├── server.log +│ └── train.log +├── node_2 +...... +├── pass-00002 +│ ├── done +│ ├── ___embedding_0__.w0 +│ ├── ___embedding_1__.w0 +...... +``` + +We can always check the container training status through logs, for example: + +``` +[root@paddle-kubernetes-node0 node_0]# cat train.log +I1116 09:10:17.123121 50 Util.cpp:155] commandline: + /usr/local/bin/../opt/paddle/bin/paddle_trainer + --nics=eth0 --port=7164 + --ports_num=2 --comment=paddle_process_by_paddle + --pservers=192.168.129.66,192.168.223.143,192.168.129.71 + --ports_num_for_sparse=2 --config=./trainer_config.py + --trainer_count=4 --num_passes=10 --use_gpu=0 + --log_period=50 --dot_period=10 --saving_period=1 + --local=0 --trainer_id=0 + --save_dir=/home/jobpath/paddle-cluster-job/output +I1116 09:10:17.123440 50 Util.cpp:130] Calling runInitFunctions +I1116 09:10:17.123764 50 Util.cpp:143] Call runInitFunctions done. +[WARNING 2016-11-16 09:10:17,227 default_decorators.py:40] please use keyword arguments in paddle config. +[INFO 2016-11-16 09:10:17,239 networks.py:1282] The input order is [movie_id, title, genres, user_id, gender, age, occupation, rating] +[INFO 2016-11-16 09:10:17,239 networks.py:1289] The output order is [__regression_cost_0__] +I1116 09:10:17.392917 50 Trainer.cpp:170] trainer mode: Normal +I1116 09:10:17.613910 50 PyDataProvider2.cpp:257] loading dataprovider dataprovider::process +I1116 09:10:17.680917 50 PyDataProvider2.cpp:257] loading dataprovider dataprovider::process +I1116 09:10:17.681543 50 GradientMachine.cpp:134] Initing parameters.. +I1116 09:10:18.012390 50 GradientMachine.cpp:141] Init parameters done. +I1116 09:10:18.018641 50 ParameterClient2.cpp:122] pserver 0 192.168.129.66:7164 +I1116 09:10:18.018950 50 ParameterClient2.cpp:122] pserver 1 192.168.129.66:7165 +I1116 09:10:18.019069 50 ParameterClient2.cpp:122] pserver 2 192.168.223.143:7164 +I1116 09:10:18.019492 50 ParameterClient2.cpp:122] pserver 3 192.168.223.143:7165 +I1116 09:10:18.019716 50 ParameterClient2.cpp:122] pserver 4 192.168.129.71:7164 +I1116 09:10:18.019836 50 ParameterClient2.cpp:122] pserver 5 192.168.129.71:7165 +``` + +It'll take around 8 hours to run this PaddlePaddle recommendation training demo on three 2 core 8 GB EC2 machine (m3.large), and the results will be 8 trained models. ###Kubernetes Cluster Tear Down -If you want to tear down the running cluster: + + +If you want to tear down the running cluster, make sure to *delete* the EFS volume first, and then use the following command: + ``` export KUBERNETES_PROVIDER=aws; /cluster/kube-down.sh @@ -313,51 +568,46 @@ export KUBERNETES_PROVIDER=aws; /cluster/kube-down This process takes about 2 to 5 minutes. ``` -[ec2-user@ip-172-31-24-50 ~]$ export KUBERNETES_PROVIDER=aws; ./kubernetes/cluster/kube-down.sh +ec2-user@ip-172-31-27-229 ~]$ export KUBERNETES_PROVIDER=aws; ./kubernetes/cluster/kube-down.sh Bringing down cluster using provider: aws -Deleting instances in VPC: vpc-fad1139d +Deleting instances in VPC: vpc-e01fc087 Deleting auto-scaling group: kubernetes-minion-group-us-west-2a Deleting auto-scaling launch configuration: kubernetes-minion-group-us-west-2a Deleting auto-scaling group: kubernetes-minion-group-us-west-2a +Deleting auto-scaling group: kubernetes-minion-group-us-west-2a Waiting for instances to be deleted -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-09d7e8824ef1f8384 to be terminated (currently shutting-down) +Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) Sleeping for 3 seconds... All instances deleted -Releasing Elastic IP: 35.162.175.115 -Deleting volume vol-04d71a810478dec0d -Cleaning up resources in VPC: vpc-fad1139d -Cleaning up security group: sg-d9280ba0 -Cleaning up security group: sg-dc280ba5 -Deleting security group: sg-d9280ba0 -Deleting security group: sg-dc280ba5 -Deleting VPC: vpc-fad1139d +Releasing Elastic IP: 35.165.155.60 +Deleting volume vol-0eba023cc1874c790 +Cleaning up resources in VPC: vpc-e01fc087 +Cleaning up security group: sg-9a7564e3 +Cleaning up security group: sg-a47564dd +Deleting security group: sg-9a7564e3 +Deleting security group: sg-a47564dd +Deleting VPC: vpc-e01fc087 Done ``` -## For experts with Kubernetes and AWS +## For Experts with Kubernetes and AWS Sometimes we might need to create or manage the cluster on AWS manually with limited privileges, so here we will explain more on what’s going on with the Kubernetes setup script. @@ -386,3 +636,4 @@ Sometimes we might need to create or manage the cluster on AWS manually with lim + From 0922f5b56ca25f09a640234c728fd878351e82b1 Mon Sep 17 00:00:00 2001 From: zhouti Date: Thu, 15 Dec 2016 20:16:18 +0800 Subject: [PATCH 006/119] Revised documentation. --- .../paddlepaddle_on_aws_with_kubernetes.md | 58 ++++++++++++------- doc/kubernetes_on_paddle.md | 2 + 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md b/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md index b4e88f6e5c..f6552c45c5 100644 --- a/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md +++ b/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md @@ -1,8 +1,14 @@ -ddlePaddle on AWS with Kubernetes +#PaddlePaddle on AWS with Kubernetes ##Prerequisites -You need an Amazon account and your user account needs the following privileges to continue: +First, you need an AWS account, please check out [this](http://docs.aws.amazon.com/lambda/latest/dg/setting-up.html) for how to setup an AWS account. + +And then you can create an user by following [this](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) instruction, you shall create an user group with following privileges, and then add the user to that group: + + + +Those previleges are: * AmazonEC2FullAccess * AmazonS3FullAccess @@ -15,24 +21,28 @@ You need an Amazon account and your user account needs the following privileges * NetworkAdministrator -![managed_policy](managed_policy.png =800x)) - -If you are not in Unites States, we also recommend creating a jump server VM instance with default amazon AMI in the same available zone as your cluster and login to jump server for the following operations, otherwise there will be some issues related to account authentication. +If you located in China, we also recommend creating a tunnel server VM instance with default amazon AMI in the same available zone as your cluster and login to tunnel server for the following steps, otherwise there will be some issues related to account authentication. ##PaddlePaddle on AWS -If you are new to Kubernetes or AWS and just want to run PaddlePaddle, you can follow these steps to start up a new cluster. +Here we will show you step by step on how to run PaddlePaddle training on AWS cluster. ###AWS Login -First configure your AWS account information: +First check out [this](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) for installing the AWS command line interface, if you use ec2 instance with default amazon AMI, the cli tool has already been installed on your machine. + + +And then configure your AWS account information: ``` aws configure ``` -Fill in the required fields: + + +Fill in the required fields (You can get your AWS aceess key id and AWS secrete access key by following [this](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html) instruction): + ``` AWS Access Key ID: YOUR_ACCESS_KEY_ID @@ -43,14 +53,19 @@ Default output format: json ``` ###Kubernetes Cluster Start Up -And then type the following command: + + +And then execute the following command after your aws login: ``` export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash ``` -By default, the script will provision a new VPC and a 4 node k8s cluster in us-west-2a (Oregon) with EC2 instances running on Debian. You can override the variables defined in `/cluster/config-default.sh` to change this behavior as follows: +By default, this command will download and unzip the latest Kubernetes release package and execute the script inside to provision a new VPC (virtual private cloud) and a four t2.micro node cluster in us-west-2a (Oregon) under that VPC. + + +You can override the variables defined in `/cluster/config-default.sh` as follows: ``` export KUBE_AWS_ZONE=us-west-2a @@ -60,7 +75,6 @@ export NODE_SIZE=m3.large export AWS_S3_REGION=us-west-2a export AWS_S3_BUCKET=mycompany-kubernetes-artifacts export KUBE_AWS_INSTANCE_PREFIX=k8s -... ``` @@ -224,8 +238,8 @@ export PATH=/platforms/linux/amd64:$PATH ``` -Now you can use administration tool `kubectl` to operate the cluster. -By default, `kubectl` will use the kubeconfig file generated during the cluster startup for authenticating against the API, the location is in `~/.kube/config`. +Now you can use Kubernetes administration tool `kubectl` to operate the cluster, let's give `kubectl get nodes` a try. + ###Setup PaddlePaddle Environment on AWS @@ -244,17 +258,18 @@ Now, we've created a cluster with following network capability: For sharing the training data across all the Kubernetes nodes, we use EFS (Elastic File System) in AWS. Ceph might be a better solution, but it requires high version of Linux kernel that might not be stable enough at this moment. We haven't automated the EFS setup at this moment, so please do the following steps: -1. Make sure you add the AmazonElasticFileSystemFullAccess policy into your AWS account. +1. Make sure you added AmazonElasticFileSystemFullAccess policy in your group. 1. Create the Elastic File System in AWS console, and attach the Kubernetes VPC with it. -![create_efs](create_efs.png =800x) + + 1. Modify the Kubernetes security group under ec2/Security Groups, add additional inbound policy "All TCP TCP 0 - 65535 0.0.0.0/0" for Kubernetes default VPC security group. -![add_security_group](add_security_group.png =800x) + 1. Follow the EC2 mount instruction to mount the disk onto all the Kubernetes nodes, we recommend to mount EFS disk onto ~/efs. -![efs_mount](efs_mount.png =800x) + Before starting the training, you should place your user config and divided training data onto EFS. When the training start, each task will copy related files from EFS into container, and it will also write the training results back onto EFS, we will show you how to place the data later in this article. @@ -552,13 +567,13 @@ I1116 09:10:18.019716 50 ParameterClient2.cpp:122] pserver 4 192.168.129.71:7 I1116 09:10:18.019836 50 ParameterClient2.cpp:122] pserver 5 192.168.129.71:7165 ``` -It'll take around 8 hours to run this PaddlePaddle recommendation training demo on three 2 core 8 GB EC2 machine (m3.large), and the results will be 8 trained models. +It'll take around 8 hours to run this PaddlePaddle recommendation training demo on three 2 core 8 GB EC2 machine (m3.large), and the results will be 10 trained models. ###Kubernetes Cluster Tear Down -If you want to tear down the running cluster, make sure to *delete* the EFS volume first, and then use the following command: +If you want to tear down the whole Kubernetes cluster, make sure to *delete* the EFS volume first (otherwise, you will get stucked on following steps), and then use the following command: ``` @@ -616,13 +631,13 @@ Sometimes we might need to create or manage the cluster on AWS manually with lim * Instances run on Debian, the official IAM, and the filesystem is aufs instead of ext4. * Kubernetes node use instance storage, no EBS get mounted. Master use a persistent volume for etcd. * Nodes are running in an Auto Scaling Group on AWS, auto-scaling itself is disabled, but if some node get terminated, it will launch another node instead. -* For networking, we use ip-per-pod model here, each pod get assigned a /24 CIDR. And the whole vpc is a /16 CIDR, No overlay network at this moment, we will add Calico solution later on. +* For networking, we use ip-per-pod model here, each pod get assigned a /24 CIDR. And the whole vpc is a /16 CIDR, No overlay network at this moment, we will use Calico solution later on. * When you create a service with Type=LoadBalancer, Kubernetes will create and ELB, and create a security group for the ELB. * Kube-proxy sets up two IAM roles, one for master called kubernetes-master, one for nodes called kubernetes-node. * All AWS resources are tagged with a tag named "KubernetesCluster", with a value that is the unique cluster-id. -###Script Details +###Script Detailed Steps * Create an s3 bucket for binaries and scripts. * Create two iam roles: kubernetes-master, kubernetes-node. @@ -636,4 +651,3 @@ Sometimes we might need to create or manage the cluster on AWS manually with lim - diff --git a/doc/kubernetes_on_paddle.md b/doc/kubernetes_on_paddle.md index 2f3109e87b..a602ff8cff 100644 --- a/doc/kubernetes_on_paddle.md +++ b/doc/kubernetes_on_paddle.md @@ -18,6 +18,7 @@ $ docker run --name quick_start_data -it paddledev/paddle:cpu-demo-latest ### Download Training Data Getting into `/root/paddle/demo/quick_start/data` Directory,using `get_data.sh` to download training data. +Then getting into `/root/paddle/demo/quick_start` Directory, using `preprocess.sh` to pre-process training data. ``` $ root@fbd1f2bb71f4:~/paddle/demo/quick_start/data# ./get_data.sh @@ -198,3 +199,4 @@ drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00012 drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00013 drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00014 ``` + From a1d94d5c7398368c8117ee42e74091263d32e8f8 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sun, 18 Dec 2016 09:17:52 -0800 Subject: [PATCH 007/119] Remove those extra trailing empty lines. --- .../usage/cluster/k8s-aws/README.md} | 3 --- .../usage/cluster/k8s-aws}/add_security_group.png | Bin .../usage/cluster/k8s-aws}/create_efs.png | Bin .../usage/cluster/k8s-aws}/efs_mount.png | Bin .../usage/cluster/k8s-aws}/managed_policy.png | Bin .../usage/cluster/k8s/k8s_en.md} | 1 - 6 files changed, 4 deletions(-) rename doc/{cluster/aws/paddlepaddle_on_aws_with_kubernetes.md => howto/usage/cluster/k8s-aws/README.md} (99%) rename doc/{cluster/aws => howto/usage/cluster/k8s-aws}/add_security_group.png (100%) rename doc/{cluster/aws => howto/usage/cluster/k8s-aws}/create_efs.png (100%) rename doc/{cluster/aws => howto/usage/cluster/k8s-aws}/efs_mount.png (100%) rename doc/{cluster/aws => howto/usage/cluster/k8s-aws}/managed_policy.png (100%) rename doc/{kubernetes_on_paddle.md => howto/usage/cluster/k8s/k8s_en.md} (99%) diff --git a/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md b/doc/howto/usage/cluster/k8s-aws/README.md similarity index 99% rename from doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md rename to doc/howto/usage/cluster/k8s-aws/README.md index f6552c45c5..13032e24bf 100644 --- a/doc/cluster/aws/paddlepaddle_on_aws_with_kubernetes.md +++ b/doc/howto/usage/cluster/k8s-aws/README.md @@ -648,6 +648,3 @@ Sometimes we might need to create or manage the cluster on AWS manually with lim * Create an EBS for master, it will be attached after the master node get up. * Launch the master with fixed ip address 172.20.0.9, and the node is initialized with Salt script, all the components get started as docker containers. * Create an auto-scaling group, it has the min and max size, it can be changed by using aws api or console, it will auto launch the kubernetes node and configure itself, connect to master, assign an internal CIDR, and the master configures the route table with the assigned CIDR. - - - diff --git a/doc/cluster/aws/add_security_group.png b/doc/howto/usage/cluster/k8s-aws/add_security_group.png similarity index 100% rename from doc/cluster/aws/add_security_group.png rename to doc/howto/usage/cluster/k8s-aws/add_security_group.png diff --git a/doc/cluster/aws/create_efs.png b/doc/howto/usage/cluster/k8s-aws/create_efs.png similarity index 100% rename from doc/cluster/aws/create_efs.png rename to doc/howto/usage/cluster/k8s-aws/create_efs.png diff --git a/doc/cluster/aws/efs_mount.png b/doc/howto/usage/cluster/k8s-aws/efs_mount.png similarity index 100% rename from doc/cluster/aws/efs_mount.png rename to doc/howto/usage/cluster/k8s-aws/efs_mount.png diff --git a/doc/cluster/aws/managed_policy.png b/doc/howto/usage/cluster/k8s-aws/managed_policy.png similarity index 100% rename from doc/cluster/aws/managed_policy.png rename to doc/howto/usage/cluster/k8s-aws/managed_policy.png diff --git a/doc/kubernetes_on_paddle.md b/doc/howto/usage/cluster/k8s/k8s_en.md similarity index 99% rename from doc/kubernetes_on_paddle.md rename to doc/howto/usage/cluster/k8s/k8s_en.md index a602ff8cff..0c3ab05b70 100644 --- a/doc/kubernetes_on_paddle.md +++ b/doc/howto/usage/cluster/k8s/k8s_en.md @@ -199,4 +199,3 @@ drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00012 drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00013 drwxr-xr-x 2 root root 4096 Oct 31 11:21 pass-00014 ``` - From ef5088725c234cb0850a61e1b63f4fc40180a9e3 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Sun, 18 Dec 2016 10:44:16 -0800 Subject: [PATCH 008/119] Rephrase the first paragraph --- doc/howto/usage/cluster/k8s-aws/README.md | 71 ++++++++++++++--------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/doc/howto/usage/cluster/k8s-aws/README.md b/doc/howto/usage/cluster/k8s-aws/README.md index 13032e24bf..011dfa4064 100644 --- a/doc/howto/usage/cluster/k8s-aws/README.md +++ b/doc/howto/usage/cluster/k8s-aws/README.md @@ -1,30 +1,47 @@ -#PaddlePaddle on AWS with Kubernetes - -##Prerequisites - -First, you need an AWS account, please check out [this](http://docs.aws.amazon.com/lambda/latest/dg/setting-up.html) for how to setup an AWS account. - -And then you can create an user by following [this](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) instruction, you shall create an user group with following privileges, and then add the user to that group: - - - -Those previleges are: - -* AmazonEC2FullAccess -* AmazonS3FullAccess -* AmazonRoute53FullAccess -* AmazonRoute53DomainsFullAccess -* AmazonElasticFileSystemFullAccess -* AmazonVPCFullAccess -* IAMUserSSHKeys -* IAMFullAccess -* NetworkAdministrator - - -If you located in China, we also recommend creating a tunnel server VM instance with default amazon AMI in the same available zone as your cluster and login to tunnel server for the following steps, otherwise there will be some issues related to account authentication. - - -##PaddlePaddle on AWS +# PaddlePaddle on AWS with Kubernetes + +## Create AWS Account and IAM Account + +To use AWS, we need to sign up an AWS account on Amazon's Web site. +An AWS account allows us to login to the AWS Console Web interface to +create IAM users and user groups. Usually, we create a user group with +privileges required to run PaddlePaddle, and we create users for +those who are going to run PaddlePaddle and add these users into the +group. IAM users can identify themselves using password and tokens, +where passwords allows users to log in to the AWS Console, and tokens +make it easy for users to submit and inspect jobs from the command +line. + +To sign up an AWS account, please +follow +[this guide](http://docs.aws.amazon.com/lambda/latest/dg/setting-up.html). +To create users and user groups under an AWS account, please +follow +[this guide](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html). + +Please be aware that this tutorial needs the following privileges in +the user group: + +- AmazonEC2FullAccess +- AmazonS3FullAccess +- AmazonRoute53FullAccess +- AmazonRoute53DomainsFullAccess +- AmazonElasticFileSystemFullAccess +- AmazonVPCFullAccess +- IAMUserSSHKeys +- IAMFullAccess +- NetworkAdministrator + + +By the time we write this tutorial, we noticed that Chinese AWS users +might suffer from authentication problems when running this tutorial. +Our solution is that we create a VM instance with the default Amazon +AMI and in the same zone as our cluster runs, so we can SSH to this VM +instance as a tunneling server and control our cluster and jobs from +it. + + +## PaddlePaddle on AWS Here we will show you step by step on how to run PaddlePaddle training on AWS cluster. From c0e687b939904b60232f3f01f7acbba3164dfbd2 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 20 Dec 2016 17:32:57 +0800 Subject: [PATCH 009/119] Refine Code --- demo/mnist/api_train.py | 12 ++++++++++++ demo/mnist/simple_mnist_network.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 demo/mnist/api_train.py create mode 100644 demo/mnist/simple_mnist_network.py diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py new file mode 100644 index 0000000000..6abb5d4e56 --- /dev/null +++ b/demo/mnist/api_train.py @@ -0,0 +1,12 @@ +import py_paddle.swig_paddle as api +from paddle.trainer.config_parser import parse_config + + +def main(): + api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores + config = parse_config('simple_mnist_network.py', '') + m = api.GradientMachine.createFromConfigProto(config.model_config) + + +if __name__ == '__main__': + main() diff --git a/demo/mnist/simple_mnist_network.py b/demo/mnist/simple_mnist_network.py new file mode 100644 index 0000000000..41f4e51657 --- /dev/null +++ b/demo/mnist/simple_mnist_network.py @@ -0,0 +1,16 @@ +from paddle.trainer_config_helpers import * + +settings(learning_rate=1e-4, learning_method=AdamOptimizer(), batch_size=1000) + +imgs = data_layer(name='pixel', size=784) + +hidden1 = fc_layer(input=imgs, size=200) +hidden2 = fc_layer(input=hidden1, size=200) + +inference = fc_layer(input=hidden2, size=10, act=SoftmaxActivation()) + +cost = classification_cost( + input=inference, label=data_layer( + name='label', size=10)) + +outputs(cost) From 8b4cbcfc1847c50228c151a485755202912e7df2 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 20 Dec 2016 22:01:28 +0800 Subject: [PATCH 010/119] Start doing mnist_train_api --- demo/mnist/api_train.py | 31 ++++++++++++++++++++++++++--- paddle/api/CMakeLists.txt | 1 + paddle/api/Paddle.swig | 3 ++- paddle/api/PaddleAPI.h | 20 +++++++++++++++++++ paddle/api/PaddleAPIPrivate.h | 27 +++++++++++++++++++++++-- paddle/api/Parameter.cpp | 16 +-------------- paddle/api/ParameterUpdater.cpp | 35 +++++++++++++++++++++++++++++++++ 7 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 paddle/api/ParameterUpdater.cpp diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 6abb5d4e56..5d4ef90f10 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -1,11 +1,36 @@ import py_paddle.swig_paddle as api -from paddle.trainer.config_parser import parse_config +import paddle.trainer.config_parser +import numpy as np + + +def init_parameter(network): + assert isinstance(network, api.GradientMachine) + for each_param in network.getParameters(): + assert isinstance(each_param, api.Parameter) + array = each_param.getBuf(api.PARAMETER_VALUE).toNumpyArrayInplace() + assert isinstance(array, np.ndarray) + for i in xrange(len(array)): + array[i] = np.random.uniform(-1.0, 1.0) def main(): api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores - config = parse_config('simple_mnist_network.py', '') - m = api.GradientMachine.createFromConfigProto(config.model_config) + config = paddle.trainer.config_parser.parse_config( + 'simple_mnist_network.py', '') + + opt_config = api.OptimizationConfig.createFromProto(config.opt_config) + _temp_optimizer_ = api.ParameterOptimizer.create(opt_config) + enable_types = _temp_optimizer_.getParameterTypes() + + m = api.GradientMachine.createFromConfigProto( + config.model_config, api.CREATE_MODE_NORMAL, enable_types) + assert isinstance(m, api.GradientMachine) + init_parameter(network=m) + + updater = api.ParameterUpdater.createLocalUpdater(opt_config) + assert isinstance(updater, api.ParameterUpdater) + updater.init(m) + updater.startPass() if __name__ == '__main__': diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index 6ad1d79e59..39fe435565 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -5,6 +5,7 @@ set(API_SOURCES Matrix.cpp Parameter.cpp ParameterOptimizer.cpp + ParameterUpdater.cpp SequenceGenerator.cpp Trainer.cpp Util.cpp diff --git a/paddle/api/Paddle.swig b/paddle/api/Paddle.swig index 9194a6371b..b0fa8beb16 100644 --- a/paddle/api/Paddle.swig +++ b/paddle/api/Paddle.swig @@ -174,6 +174,7 @@ namespace std { %newobject Parameter::getConfig; %newobject ParameterOptimizer::create; %newobject ParameterOptimizer::needSpecialTraversal; +%newobject ParameterUpdater::createLocalUpdater; %feature("director") UpdateCallback; %feature("autodoc", 1); // To generate method stub, for code hint in ide @@ -193,4 +194,4 @@ namespace std { %ignore OptimizationConfigPrivate; %ignore ParameterTraverseCallbackPrivate; %include "utils/GlobalConstants.h" -%include "api/PaddleAPI.h" \ No newline at end of file +%include "api/PaddleAPI.h" diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 84a66719c3..bd413eb1e9 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -519,6 +519,7 @@ private: friend class TrainerConfig; friend class ParameterOptimizer; + friend class ParameterUpdater; friend class Trainer; }; @@ -557,6 +558,7 @@ private: ParameterPrivate* m; friend class UpdateCallbackWrapper; friend class GradientMachine; + friend class ParameterUpdater; }; struct ModelConfigPrivate; @@ -772,6 +774,24 @@ private: // Not to use c++ 11 init-list, so we use static var as function default arg. static std::vector defaultParamTypes; friend class Trainer; + friend class ParameterUpdater; +}; + +struct ParameterUpdaterPrivate; +class ParameterUpdater { +private: + ParameterUpdater(); + +public: + static ParameterUpdater* createLocalUpdater(OptimizationConfig* config); + ~ParameterUpdater(); + + void init(const GradientMachine& gm); + + void startPass(); + +private: + ParameterUpdaterPrivate* m; }; struct TrainerPrivate; diff --git a/paddle/api/PaddleAPIPrivate.h b/paddle/api/PaddleAPIPrivate.h index d2b56fc41c..905668a62f 100644 --- a/paddle/api/PaddleAPIPrivate.h +++ b/paddle/api/PaddleAPIPrivate.h @@ -11,11 +11,13 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - +#pragma once +#include +#include "PaddleAPI.h" #include "paddle/gserver/gradientmachines/GradientMachine.h" #include "paddle/trainer/TrainerConfigHelper.h" -#pragma once +#include "paddle/parameter/ParameterUpdaterBase.h" struct GradientMachinePrivate { std::shared_ptr machine; @@ -65,3 +67,24 @@ struct ArgumentsPrivate { return *(std::shared_ptr*)(rawPtr); } }; + +struct ParameterUpdaterPrivate { + std::unique_ptr updater; +}; + +struct ParameterPrivate { + std::shared_ptr sharedPtr; + paddle::Parameter* rawPtr; // rawPtr only used in ParameterUpdater, + // in other situation sharedPtr should + // contains value. + + ParameterPrivate() : sharedPtr(nullptr), rawPtr(nullptr) {} + + paddle::Parameter* getPtr() { + if (sharedPtr) { + return sharedPtr.get(); + } else { + return rawPtr; + } + } +}; diff --git a/paddle/api/Parameter.cpp b/paddle/api/Parameter.cpp index 4eed00a84a..41cf50043c 100644 --- a/paddle/api/Parameter.cpp +++ b/paddle/api/Parameter.cpp @@ -14,21 +14,7 @@ limitations under the License. */ #include "paddle/parameter/Parameter.h" #include "PaddleAPI.h" - -struct ParameterPrivate { - std::shared_ptr sharedPtr; - paddle::Parameter* rawPtr; - - ParameterPrivate() : sharedPtr(nullptr), rawPtr(nullptr) {} - - paddle::Parameter* getPtr() { - if (sharedPtr) { - return sharedPtr.get(); - } else { - return rawPtr; - } - } -}; +#include "PaddleAPIPrivate.h" Parameter::Parameter() : m(new ParameterPrivate()) {} diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp new file mode 100644 index 0000000000..af5b746a7c --- /dev/null +++ b/paddle/api/ParameterUpdater.cpp @@ -0,0 +1,35 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include "PaddleAPI.h" + +#include "PaddleAPIPrivate.h" +#include "paddle/trainer/ThreadParameterUpdater.h" + +ParameterUpdater::ParameterUpdater() : m(new ParameterUpdaterPrivate()) {} + +ParameterUpdater *ParameterUpdater::createLocalUpdater( + OptimizationConfig *config) { + auto param = new ParameterUpdater(); + param->m->updater.reset(new paddle::SgdThreadUpdater(config->m->getConfig())); + return param; +} + +ParameterUpdater::~ParameterUpdater() { delete m; } + +void ParameterUpdater::init(const GradientMachine &gm) { + m->updater->init(gm.m->machine->getParameters()); +} + +void ParameterUpdater::startPass() { m->updater->startPass(); } From 025e3e94d2b216cc278de103cbef27b851274bf5 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 20 Dec 2016 23:00:34 +0800 Subject: [PATCH 011/119] Add GradientMachine::start/finish to API --- demo/mnist/api_train.py | 7 ++++++- paddle/api/GradientMachine.cpp | 4 ++++ paddle/api/PaddleAPI.h | 9 +++++++++ paddle/api/ParameterUpdater.cpp | 2 ++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 5d4ef90f10..b061cfb2b8 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -30,7 +30,12 @@ def main(): updater = api.ParameterUpdater.createLocalUpdater(opt_config) assert isinstance(updater, api.ParameterUpdater) updater.init(m) - updater.startPass() + m.start() + + for _ in xrange(100): + updater.startPass() + + m.finish() if __name__ == '__main__': diff --git a/paddle/api/GradientMachine.cpp b/paddle/api/GradientMachine.cpp index 297eaa19bb..2cece21097 100644 --- a/paddle/api/GradientMachine.cpp +++ b/paddle/api/GradientMachine.cpp @@ -64,6 +64,10 @@ GradientMachine* GradientMachine::createByModelConfig( return GradientMachine::createFromPaddleModelPtr(confPtr, mode, types); } +void GradientMachine::start() { m->machine->start(); } + +void GradientMachine::finish() { m->machine->finish(); } + void GradientMachine::forward(const Arguments& inArgs, Arguments* outArgs, PassType passType) { diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index bd413eb1e9..c074325091 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -716,6 +716,13 @@ public: GradientMatchineCreateMode mode = CREATE_MODE_NORMAL, const std::vector& parameterTypes = defaultParamTypes); + /** + * @brief finish + */ + void finish(); + + void start(); + /** * The forward stage of GradientMachine. * @@ -790,6 +797,8 @@ public: void startPass(); + void finishPass(); + private: ParameterUpdaterPrivate* m; }; diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index af5b746a7c..3b626c0507 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -33,3 +33,5 @@ void ParameterUpdater::init(const GradientMachine &gm) { } void ParameterUpdater::startPass() { m->updater->startPass(); } + +void ParameterUpdater::finishPass() {} From 697a01a2f0cc57b7c759485c3aabe2fd40f0a703 Mon Sep 17 00:00:00 2001 From: water1981 Date: Wed, 21 Dec 2016 11:38:12 +0800 Subject: [PATCH 012/119] =?UTF-8?q?=E6=8C=89=E7=85=A7=E8=AF=84=E5=AE=A1?= =?UTF-8?q?=E4=BA=BA=E7=9A=84=E6=84=8F=E8=A7=81=E8=BF=9B=E8=A1=8C=E4=BA=86?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/tutorials/embedding_model/index_cn.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/doc/tutorials/embedding_model/index_cn.md b/doc/tutorials/embedding_model/index_cn.md index f0ed8a65cc..55f78ba1ef 100644 --- a/doc/tutorials/embedding_model/index_cn.md +++ b/doc/tutorials/embedding_model/index_cn.md @@ -6,13 +6,13 @@ ## 介绍 ### ### 中文字典 ### -我们的字典采用内部的分词工具对百度知道和百度百科的语料进行分词后产生。分词风格如下: "《红楼梦》"将被分为 "《","红楼梦","》",和 "《红楼梦》"。字典采用UTF8编码,输出有2列:词本身和词频。字典共包含 3206325个词和3个特殊标记: +我们的字典使用内部的分词工具对百度知道和百度百科的语料进行分词后产生。分词风格如下: "《红楼梦》"将被分为 "《","红楼梦","》",和 "《红楼梦》"。字典采用UTF8编码,输出有2列:词本身和词频。字典共包含 3206325个词和3个特殊标记: - ``: 分词序列的开始 - ``: 分词序列的结束 - ``: 未知词 ### 中文词向量的预训练模型 ### -如下图,遵循文章 [A Neural Probabilistic Language Model](http://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf)中介绍的方法,我们的词向量模型的结构采用:6元上下文作为输入层->全连接层->softmax层 。我们的字典包含4个维度的词向量编码,分别为:32维、64维、128维和256维。 +遵循文章 [A Neural Probabilistic Language Model](http://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf)中介绍的方法,模型采用 n-gram 语言模型,结构如下图:6元上下文作为输入层->全连接层->softmax层 。对应于字典,我们预训练得到4种不同维度的词向量,分别为:32维、64维、128维和256维。

![](./neural-n-gram-model.png)
Figure 1. neural-n-gram-model
@@ -23,7 +23,7 @@ ./pre_DictAndModel.sh ## 中文短语改写的例子 ## -以下示范如何使用预训练的中文字典和词向量模型进行短语改写。 +以下示范如何使用预训练的中文字典和词向量进行短语改写。 ### 数据的准备和预处理 ### 首先,运行以下的命令下载数据集。该数据集(utf8编码)包含20个训练样例,5个测试样例和2个生成式样例。 @@ -40,15 +40,14 @@ ### 使用用户指定的词向量字典 ### -从用户指定的词向量字典中抽取模型的命令如下: - +使用如下命令,从预训练模型中,根据用户指定的字典,抽取对应的词向量构成新的词表 cd $PADDLE_ROOT/demo/model_zoo/embedding python extract_para.py --preModel PREMODEL --preDict PREDICT --usrModel USRMODEL--usrDict USRDICT -d DIM -- `--preModel PREMODEL`: 预训练词向量字典模型的名字 -- `--preDict PREDICT`: 预训练(词向量)字典的名字 -- `--usrModel USRMODEL`: (用户指定的)待抽取的词向量模型的名字 -- `--usrDict USRDICT`: 用户指定的字典的名字 +- `--preModel PREMODEL`: 预训练词向量字典模型的路径 +- `--preDict PREDICT`: 预训练模型使用的字典的路径 +- `--usrModel USRMODEL`: (抽取出的新词表的保存路径 +- `--usrDict USRDICT`: 用户指定新的字典的路径,用于构成新的词表 - `-d DIM`: 参数(词向量)的维度 此处,你也可以简单的运行以下的命令: @@ -91,7 +90,7 @@ 其中,`train.sh` 与`demo/seqToseq/translation/train.sh` 基本相同,只有2个配置不一样: - `--init_model_path`: 初始化模型的路径配置为`data/paraphrase_modeldata/paraphrase_model` -- `--load_missing_parameter_strategy`: 如果参数模型文件缺失,初始化时,除词向量模型外的参数将使用正态分布 +- `--load_missing_parameter_strategy`:除词向量模型外的参数将使用正态分布” 改为 “除词向量模型外的参数将使用正态分布随机初始化 如果用户想要了解详细的数据集的格式、模型的结构和训练过程,请查看 [Text generation Tutorial](../text_generation/text_generation.md). From fdc91953969db693e9052004e836ab9bbcb779a2 Mon Sep 17 00:00:00 2001 From: water1981 Date: Wed, 21 Dec 2016 11:38:48 +0800 Subject: [PATCH 013/119] Update index_cn.md --- doc/tutorials/embedding_model/index_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorials/embedding_model/index_cn.md b/doc/tutorials/embedding_model/index_cn.md index 55f78ba1ef..bfa0c7af85 100644 --- a/doc/tutorials/embedding_model/index_cn.md +++ b/doc/tutorials/embedding_model/index_cn.md @@ -46,7 +46,7 @@ - `--preModel PREMODEL`: 预训练词向量字典模型的路径 - `--preDict PREDICT`: 预训练模型使用的字典的路径 -- `--usrModel USRMODEL`: (抽取出的新词表的保存路径 +- `--usrModel USRMODEL`: 抽取出的新词表的保存路径 - `--usrDict USRDICT`: 用户指定新的字典的路径,用于构成新的词表 - `-d DIM`: 参数(词向量)的维度 From 0029166acbe6caf21a643601897d2943f73513d6 Mon Sep 17 00:00:00 2001 From: water1981 Date: Wed, 21 Dec 2016 12:24:26 +0800 Subject: [PATCH 014/119] modify --- doc/tutorials/embedding_model/index_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorials/embedding_model/index_cn.md b/doc/tutorials/embedding_model/index_cn.md index bfa0c7af85..bf292d2ec1 100644 --- a/doc/tutorials/embedding_model/index_cn.md +++ b/doc/tutorials/embedding_model/index_cn.md @@ -90,7 +90,7 @@ 其中,`train.sh` 与`demo/seqToseq/translation/train.sh` 基本相同,只有2个配置不一样: - `--init_model_path`: 初始化模型的路径配置为`data/paraphrase_modeldata/paraphrase_model` -- `--load_missing_parameter_strategy`:除词向量模型外的参数将使用正态分布” 改为 “除词向量模型外的参数将使用正态分布随机初始化 +- `--load_missing_parameter_strategy`:如果参数模型文件缺失,除词向量模型外的参数将使用正态分布随机初始化 如果用户想要了解详细的数据集的格式、模型的结构和训练过程,请查看 [Text generation Tutorial](../text_generation/text_generation.md). From 49ceebe13c80350ea3a55b92309f5b5d2b933d51 Mon Sep 17 00:00:00 2001 From: water1981 Date: Wed, 21 Dec 2016 12:26:56 +0800 Subject: [PATCH 015/119] modify load_missing_parameter_strategy --- doc/tutorials/embedding_model/index_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorials/embedding_model/index_cn.md b/doc/tutorials/embedding_model/index_cn.md index bf292d2ec1..bf86735050 100644 --- a/doc/tutorials/embedding_model/index_cn.md +++ b/doc/tutorials/embedding_model/index_cn.md @@ -40,7 +40,7 @@ ### 使用用户指定的词向量字典 ### -使用如下命令,从预训练模型中,根据用户指定的字典,抽取对应的词向量构成新的词表 +使用如下命令,从预训练模型中,根据用户指定的字典,抽取对应的词向量构成新的词表: cd $PADDLE_ROOT/demo/model_zoo/embedding python extract_para.py --preModel PREMODEL --preDict PREDICT --usrModel USRMODEL--usrDict USRDICT -d DIM From 20f4d6b6273b1fed2208b579dfc51189ca2467b2 Mon Sep 17 00:00:00 2001 From: water1981 Date: Wed, 21 Dec 2016 12:31:09 +0800 Subject: [PATCH 016/119] modify the url reference correct the original to "../text_generation/index_cn.md" --- doc/tutorials/embedding_model/index_cn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorials/embedding_model/index_cn.md b/doc/tutorials/embedding_model/index_cn.md index bf86735050..fe800308d8 100644 --- a/doc/tutorials/embedding_model/index_cn.md +++ b/doc/tutorials/embedding_model/index_cn.md @@ -92,7 +92,7 @@ - `--init_model_path`: 初始化模型的路径配置为`data/paraphrase_modeldata/paraphrase_model` - `--load_missing_parameter_strategy`:如果参数模型文件缺失,除词向量模型外的参数将使用正态分布随机初始化 -如果用户想要了解详细的数据集的格式、模型的结构和训练过程,请查看 [Text generation Tutorial](../text_generation/text_generation.md). +如果用户想要了解详细的数据集的格式、模型的结构和训练过程,请查看 [Text generation Tutorial](../text_generation/index_cn.md). ## 可选功能 ## ### 观测词向量 From 27d87db6a0f937a7fa22b03e3d18844f894698e1 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 12:54:11 +0800 Subject: [PATCH 017/119] Wait for reading data. --- demo/mnist/api_train.py | 2 ++ paddle/api/ParameterUpdater.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index b061cfb2b8..59043ce6c4 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -35,6 +35,8 @@ def main(): for _ in xrange(100): updater.startPass() + updater.finishPass() + m.finish() diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 3b626c0507..4edec78b4a 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -34,4 +34,4 @@ void ParameterUpdater::init(const GradientMachine &gm) { void ParameterUpdater::startPass() { m->updater->startPass(); } -void ParameterUpdater::finishPass() {} +void ParameterUpdater::finishPass() { m->updater->finishPass(); } From 9f5e742b6d4018cf5022a6718d5913f2459cf95e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 12:57:19 +0800 Subject: [PATCH 018/119] A tiny fix in PyDataProvider2 * hidden decorator kwargs in DataProvider.__init__ * also add unit test for this. --- paddle/gserver/tests/test_PyDataProvider2.py | 2 +- python/paddle/trainer/PyDataProvider2.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/paddle/gserver/tests/test_PyDataProvider2.py b/paddle/gserver/tests/test_PyDataProvider2.py index f7b540013e..2e6225519f 100644 --- a/paddle/gserver/tests/test_PyDataProvider2.py +++ b/paddle/gserver/tests/test_PyDataProvider2.py @@ -17,7 +17,7 @@ import random from paddle.trainer.PyDataProvider2 import * -@provider(input_types=[dense_vector(200, seq_type=SequenceType.NO_SEQUENCE)]) +@provider(slots=[dense_vector(200, seq_type=SequenceType.NO_SEQUENCE)]) def test_dense_no_seq(setting, filename): for i in xrange(200): yield [(float(j - 100) * float(i + 1)) / 200.0 for j in xrange(200)] diff --git a/python/paddle/trainer/PyDataProvider2.py b/python/paddle/trainer/PyDataProvider2.py index de266bb5d3..5ca4bcbca6 100644 --- a/python/paddle/trainer/PyDataProvider2.py +++ b/python/paddle/trainer/PyDataProvider2.py @@ -232,7 +232,7 @@ def provider(input_types=None, check=False, check_fail_continue=False, init_hook=None, - **kwargs): + **outter_kwargs): """ Provider decorator. Use it to make a function into PyDataProvider2 object. In this function, user only need to get each sample for some train/test @@ -318,10 +318,10 @@ def provider(input_types=None, self.logger = logging.getLogger("") self.logger.setLevel(logging.INFO) self.input_types = None - if 'slots' in kwargs: + if 'slots' in outter_kwargs: self.logger.warning('setting slots value is deprecated, ' 'please use input_types instead.') - self.slots = kwargs['slots'] + self.slots = outter_kwargs['slots'] self.slots = input_types self.should_shuffle = should_shuffle From 5f6c4af3a544b828fe7c71c98164f9e8b6994f5b Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 13:27:32 +0800 Subject: [PATCH 019/119] Try to read data in mnist --- demo/mnist/api_train.py | 29 +++++++++++++++++++++++++++++ demo/mnist/mnist_provider.py | 28 +++------------------------- demo/mnist/mnist_util.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 25 deletions(-) create mode 100644 demo/mnist/mnist_util.py diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 59043ce6c4..e508af7a0c 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -1,6 +1,9 @@ import py_paddle.swig_paddle as api +from py_paddle import DataProviderConverter +import paddle.trainer.PyDataProvider2 as dp import paddle.trainer.config_parser import numpy as np +from mnist_util import read_from_mnist def init_parameter(network): @@ -13,6 +16,22 @@ def init_parameter(network): array[i] = np.random.uniform(-1.0, 1.0) +def generator_to_batch(generator, batch_size): + ret_val = list() + for each_item in generator: + ret_val.append(each_item) + if len(ret_val) == batch_size: + yield ret_val + ret_val = list() + if len(ret_val) != 0: + yield ret_val + + +def input_order_converter(generator): + for each_item in generator: + yield each_item['pixel'], each_item['label'] + + def main(): api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores config = paddle.trainer.config_parser.parse_config( @@ -30,10 +49,20 @@ def main(): updater = api.ParameterUpdater.createLocalUpdater(opt_config) assert isinstance(updater, api.ParameterUpdater) updater.init(m) + + converter = DataProviderConverter( + input_types=[dp.dense_vector(784), dp.integer_value(10)]) + + train_file = './data/raw_data/train' + m.start() for _ in xrange(100): updater.startPass() + train_data_generator = input_order_converter( + read_from_mnist(train_file)) + for data_batch in generator_to_batch(train_data_generator, 128): + inArgs = converter(data_batch) updater.finishPass() diff --git a/demo/mnist/mnist_provider.py b/demo/mnist/mnist_provider.py index 4635833d36..888cfef1e7 100644 --- a/demo/mnist/mnist_provider.py +++ b/demo/mnist/mnist_provider.py @@ -1,5 +1,5 @@ from paddle.trainer.PyDataProvider2 import * -import numpy +from mnist_util import read_from_mnist # Define a py data provider @@ -8,27 +8,5 @@ import numpy 'label': integer_value(10)}, cache=CacheType.CACHE_PASS_IN_MEM) def process(settings, filename): # settings is not used currently. - imgf = filename + "-images-idx3-ubyte" - labelf = filename + "-labels-idx1-ubyte" - f = open(imgf, "rb") - l = open(labelf, "rb") - - f.read(16) - l.read(8) - - # Define number of samples for train/test - if "train" in filename: - n = 60000 - else: - n = 10000 - - images = numpy.fromfile( - f, 'ubyte', count=n * 28 * 28).reshape((n, 28 * 28)).astype('float32') - images = images / 255.0 * 2.0 - 1.0 - labels = numpy.fromfile(l, 'ubyte', count=n).astype("int") - - for i in xrange(n): - yield {"pixel": images[i, :], 'label': labels[i]} - - f.close() - l.close() + for each in read_from_mnist(filename): + yield each diff --git a/demo/mnist/mnist_util.py b/demo/mnist/mnist_util.py new file mode 100644 index 0000000000..3fd88ae7ed --- /dev/null +++ b/demo/mnist/mnist_util.py @@ -0,0 +1,30 @@ +import numpy + +__all__ = ['read_from_mnist'] + + +def read_from_mnist(filename): + imgf = filename + "-images-idx3-ubyte" + labelf = filename + "-labels-idx1-ubyte" + f = open(imgf, "rb") + l = open(labelf, "rb") + + f.read(16) + l.read(8) + + # Define number of samples for train/test + if "train" in filename: + n = 60000 + else: + n = 10000 + + images = numpy.fromfile( + f, 'ubyte', count=n * 28 * 28).reshape((n, 28 * 28)).astype('float32') + images = images / 255.0 * 2.0 - 1.0 + labels = numpy.fromfile(l, 'ubyte', count=n).astype("int") + + for i in xrange(n): + yield {"pixel": images[i, :], 'label': labels[i]} + + f.close() + l.close() From 36d1e6178c4e6d563cf1be644d1a828b577b7f28 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 13:38:31 +0800 Subject: [PATCH 020/119] Use numpy in DenseScanner. --- paddle/py_paddle/dataprovider_converter.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/paddle/py_paddle/dataprovider_converter.py b/paddle/py_paddle/dataprovider_converter.py index edcefba6a8..981d10afda 100644 --- a/paddle/py_paddle/dataprovider_converter.py +++ b/paddle/py_paddle/dataprovider_converter.py @@ -15,6 +15,7 @@ import paddle.trainer.PyDataProvider2 as dp2 import collections import swig_paddle +import numpy __all__ = ['DataProviderConverter'] @@ -35,18 +36,18 @@ class IScanner(object): class DenseScanner(IScanner): def __init__(self, input_type, pos): IScanner.__init__(self, input_type, pos) - self.__mat__ = [] - self.__height__ = 0 + self.__mat__ = None def scan(self, dat): - self.__mat__.extend(dat) - self.__height__ += 1 + if self.__mat__ is None: + self.__mat__ = numpy.array([dat], dtype='float32') + else: + self.__mat__ = numpy.append(self.__mat__, [dat], axis=0) def finish_scan(self, argument): assert isinstance(argument, swig_paddle.Arguments) assert isinstance(self.input_type, dp2.InputType) - m = swig_paddle.Matrix.createDense(self.__mat__, self.__height__, - self.input_type.dim, False) + m = swig_paddle.Matrix.createDenseFromNumpy(self.__mat__, True, False) argument.setSlotValue(self.pos, m) From 20249e8e65aca17abaa9bbee9ab660e3573e21cf Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 13:55:44 +0800 Subject: [PATCH 021/119] Try expose ParamUpdater::update --- demo/mnist/api_train.py | 3 +-- paddle/api/PaddleAPI.h | 6 ++++++ paddle/api/ParameterUpdater.cpp | 13 +++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index e508af7a0c..ef8b20a48d 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -45,7 +45,6 @@ def main(): config.model_config, api.CREATE_MODE_NORMAL, enable_types) assert isinstance(m, api.GradientMachine) init_parameter(network=m) - updater = api.ParameterUpdater.createLocalUpdater(opt_config) assert isinstance(updater, api.ParameterUpdater) updater.init(m) @@ -62,7 +61,7 @@ def main(): train_data_generator = input_order_converter( read_from_mnist(train_file)) for data_batch in generator_to_batch(train_data_generator, 128): - inArgs = converter(data_batch) + trainRole = updater.startBatch(len(data_batch)) updater.finishPass() diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index c074325091..165997ba34 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -799,6 +799,12 @@ public: void finishPass(); + PassType startBatch(int64_t batchSize); + + void finishBatch(float cost); + + void update(Parameter* param); + private: ParameterUpdaterPrivate* m; }; diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 4edec78b4a..e5d07b8178 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -35,3 +35,16 @@ void ParameterUpdater::init(const GradientMachine &gm) { void ParameterUpdater::startPass() { m->updater->startPass(); } void ParameterUpdater::finishPass() { m->updater->finishPass(); } + +PassType ParameterUpdater::startBatch(int64_t batchSize) { + return m->updater->startBatch(batchSize); +} + +void ParameterUpdater::finishBatch(float cost) { + m->updater->finishBatch(cost); +} + +void ParameterUpdater::update(Parameter *param) { + auto paddleParam = param->m->getPtr(); + m->updater->update(paddleParam); +} From 05ab22c332e615f3c81f4d4b2c9b47f71229c71c Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 14:22:31 +0800 Subject: [PATCH 022/119] A simplest train file for mnist added. --- demo/mnist/api_train.py | 16 +++++++++++++++- paddle/api/PaddleAPI.h | 2 +- paddle/api/ParameterUpdater.cpp | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index ef8b20a48d..425c5f897a 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -58,11 +58,25 @@ def main(): for _ in xrange(100): updater.startPass() + outArgs = api.Arguments.createArguments(0) train_data_generator = input_order_converter( read_from_mnist(train_file)) - for data_batch in generator_to_batch(train_data_generator, 128): + for batch_id, data_batch in enumerate( + generator_to_batch(train_data_generator, 256)): trainRole = updater.startBatch(len(data_batch)) + def update_callback(param): + updater.update(param) + + m.forwardBackward( + converter(data_batch), outArgs, trainRole, update_callback) + + cost_vec = outArgs.getSlotValue(0) + cost_vec = cost_vec.copyToNumpyMat() + cost = cost_vec.sum() / len(data_batch) + print 'Batch id', batch_id, 'with cost=', cost + updater.finishBatch(cost) + updater.finishPass() m.finish() diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 165997ba34..cc49e6a09d 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -799,7 +799,7 @@ public: void finishPass(); - PassType startBatch(int64_t batchSize); + PassType startBatch(size_t batchSize); void finishBatch(float cost); diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index e5d07b8178..fba4762024 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -36,8 +36,8 @@ void ParameterUpdater::startPass() { m->updater->startPass(); } void ParameterUpdater::finishPass() { m->updater->finishPass(); } -PassType ParameterUpdater::startBatch(int64_t batchSize) { - return m->updater->startBatch(batchSize); +PassType ParameterUpdater::startBatch(size_t batchSize) { + return m->updater->startBatch((int64_t)batchSize); } void ParameterUpdater::finishBatch(float cost) { From 1f4f04427d5f34e48a0a30b9137a882a6f1b571c Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 12:57:19 +0800 Subject: [PATCH 023/119] A tiny fix in PyDataProvider2 * hidden decorator kwargs in DataProvider.__init__ * also add unit test for this. --- paddle/gserver/tests/test_PyDataProvider2.py | 2 +- python/paddle/trainer/PyDataProvider2.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/paddle/gserver/tests/test_PyDataProvider2.py b/paddle/gserver/tests/test_PyDataProvider2.py index f7b540013e..2e6225519f 100644 --- a/paddle/gserver/tests/test_PyDataProvider2.py +++ b/paddle/gserver/tests/test_PyDataProvider2.py @@ -17,7 +17,7 @@ import random from paddle.trainer.PyDataProvider2 import * -@provider(input_types=[dense_vector(200, seq_type=SequenceType.NO_SEQUENCE)]) +@provider(slots=[dense_vector(200, seq_type=SequenceType.NO_SEQUENCE)]) def test_dense_no_seq(setting, filename): for i in xrange(200): yield [(float(j - 100) * float(i + 1)) / 200.0 for j in xrange(200)] diff --git a/python/paddle/trainer/PyDataProvider2.py b/python/paddle/trainer/PyDataProvider2.py index de266bb5d3..c918fa78ac 100644 --- a/python/paddle/trainer/PyDataProvider2.py +++ b/python/paddle/trainer/PyDataProvider2.py @@ -232,7 +232,7 @@ def provider(input_types=None, check=False, check_fail_continue=False, init_hook=None, - **kwargs): + **outter_kwargs): """ Provider decorator. Use it to make a function into PyDataProvider2 object. In this function, user only need to get each sample for some train/test @@ -318,11 +318,15 @@ def provider(input_types=None, self.logger = logging.getLogger("") self.logger.setLevel(logging.INFO) self.input_types = None - if 'slots' in kwargs: + if 'slots' in outter_kwargs: self.logger.warning('setting slots value is deprecated, ' 'please use input_types instead.') - self.slots = kwargs['slots'] - self.slots = input_types + self.slots = outter_kwargs['slots'] + if input_types is not None: + self.slots = input_types + + assert self.slots is not None, \ + "Data Provider's input_types must be set" self.should_shuffle = should_shuffle true_table = [1, 't', 'true', 'on'] From eaba2e2eff6c9bf1bbdff452b4be636ef0b8da9a Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 21:56:37 +0800 Subject: [PATCH 024/119] Expose Evaluator API --- demo/mnist/api_train.py | 6 +++--- paddle/api/CMakeLists.txt | 10 ++++++++++ paddle/api/Evaluator.cpp | 29 +++++++++++++++++++++++++++++ paddle/api/GradientMachine.cpp | 10 ++++++++++ paddle/api/Paddle.swig | 2 ++ paddle/api/PaddleAPI.h | 27 ++++++++++++++++++++++++++- paddle/api/PaddleAPIPrivate.h | 11 +++++++++-- paddle/api/ParameterUpdater.cpp | 2 +- 8 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 paddle/api/Evaluator.cpp diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 425c5f897a..52cc13c5a3 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -62,14 +62,14 @@ def main(): train_data_generator = input_order_converter( read_from_mnist(train_file)) for batch_id, data_batch in enumerate( - generator_to_batch(train_data_generator, 256)): + generator_to_batch(train_data_generator, 2048)): trainRole = updater.startBatch(len(data_batch)) - def update_callback(param): + def updater_callback(param): updater.update(param) m.forwardBackward( - converter(data_batch), outArgs, trainRole, update_callback) + converter(data_batch), outArgs, trainRole, updater_callback) cost_vec = outArgs.getSlotValue(0) cost_vec = cost_vec.copyToNumpyMat() diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index 39fe435565..a7f17e186b 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -1,6 +1,7 @@ set(API_SOURCES Arguments.cpp ConfigParser.cpp + Evaluator.cpp GradientMachine.cpp Matrix.cpp Parameter.cpp @@ -63,6 +64,15 @@ install(DIRECTORY ${PROJ_ROOT}/paddle/dist/ add_custom_target(python_api_wheel ALL DEPENDS ${PROJ_ROOT}/paddle/dist/.timestamp) +add_dependencies(python_api_wheel python_swig_sources + paddle_parameter + paddle_math + paddle_utils + paddle_gserver + paddle_pserver + paddle_trainer + paddle_api + paddle_cuda) if(WITH_TESTING) add_subdirectory(test) diff --git a/paddle/api/Evaluator.cpp b/paddle/api/Evaluator.cpp new file mode 100644 index 0000000000..c30e098763 --- /dev/null +++ b/paddle/api/Evaluator.cpp @@ -0,0 +1,29 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ +#include +#include "PaddleAPI.h" +#include "PaddleAPIPrivate.h" + +Evaluator::Evaluator() : m(new EvaluatorPrivate()) {} +Evaluator::~Evaluator() { delete m; } + +void Evaluator::start() { m->rawPtr->start(); } + +void Evaluator::finish() { m->rawPtr->finish(); } + +std::string Evaluator::toString() { + std::ostringstream sout; + m->rawPtr->printStats(sout); + return sout.str(); +} diff --git a/paddle/api/GradientMachine.cpp b/paddle/api/GradientMachine.cpp index 2cece21097..0d1e175296 100644 --- a/paddle/api/GradientMachine.cpp +++ b/paddle/api/GradientMachine.cpp @@ -162,3 +162,13 @@ SequenceGenerator* GradientMachine::asSequenceGenerator( r->setBeamSize(beam_size); return r; } + +Evaluator* GradientMachine::makeEvaluator() { + auto ev = new Evaluator(); + ev->m->rawPtr = m->machine->makeEvaluator(); + return ev; +} + +void GradientMachine::eval(Evaluator* evaluator) { + m->machine->eval(evaluator->m->rawPtr); +} diff --git a/paddle/api/Paddle.swig b/paddle/api/Paddle.swig index b0fa8beb16..7a110a90b8 100644 --- a/paddle/api/Paddle.swig +++ b/paddle/api/Paddle.swig @@ -97,6 +97,7 @@ namespace std { %rename(__setitem__) Vector::set; %rename(__len__) Vector::getSize; %rename(__call__) ParameterTraverseCallback::apply; +%rename(__repr__) Evaluator::toString; %apply (float* INPLACE_ARRAY2, int DIM1, int DIM2) { (float* data, int dim1, int dim2) @@ -167,6 +168,7 @@ namespace std { %newobject GradientMachine::asSequenceGenerator; %newobject GradientMachine::getParameter; %newobject GradientMachine::getLayerOutput; +%newobject GradientMachine::makeEvaluator; %newobject TrainerConfig::createFromTrainerConfigFile; %newobject TrainerConfig::getModelConfig; %newobject TrainerConfig::getOptimizationConfig; diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index cc49e6a09d..413c385146 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -685,7 +685,7 @@ private: }; class SequenceGenerator; - +class Evaluator; struct GradientMachinePrivate; class GradientMachine { private: @@ -770,6 +770,10 @@ public: size_t max_length = 100UL, size_t beam_size = -1UL); + Evaluator* makeEvaluator(); + + void eval(Evaluator* evaluator); + private: GradientMachinePrivate* m; @@ -809,6 +813,27 @@ private: ParameterUpdaterPrivate* m; }; +struct EvaluatorPrivate; +class Evaluator { +private: + Evaluator(); + DISABLE_COPY_AND_ASSIGN(Evaluator); + +public: + ~Evaluator(); + + void start(); + + void finish(); + + std::string toString(); + +private: + EvaluatorPrivate* m; + + friend class GradientMachine; +}; + struct TrainerPrivate; class Trainer { private: diff --git a/paddle/api/PaddleAPIPrivate.h b/paddle/api/PaddleAPIPrivate.h index 905668a62f..f41352bfec 100644 --- a/paddle/api/PaddleAPIPrivate.h +++ b/paddle/api/PaddleAPIPrivate.h @@ -14,10 +14,10 @@ limitations under the License. */ #pragma once #include #include "PaddleAPI.h" +#include "paddle/gserver/evaluators/Evaluator.h" #include "paddle/gserver/gradientmachines/GradientMachine.h" -#include "paddle/trainer/TrainerConfigHelper.h" - #include "paddle/parameter/ParameterUpdaterBase.h" +#include "paddle/trainer/TrainerConfigHelper.h" struct GradientMachinePrivate { std::shared_ptr machine; @@ -88,3 +88,10 @@ struct ParameterPrivate { } } }; + +struct EvaluatorPrivate { + paddle::Evaluator* rawPtr; + + EvaluatorPrivate() : rawPtr(nullptr) {} + ~EvaluatorPrivate() { delete rawPtr; } +}; diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index fba4762024..91c8392762 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -29,7 +29,7 @@ ParameterUpdater *ParameterUpdater::createLocalUpdater( ParameterUpdater::~ParameterUpdater() { delete m; } void ParameterUpdater::init(const GradientMachine &gm) { - m->updater->init(gm.m->machine->getParameters()); + m->updater->init(gm.m->machine->getNonStaticParameters()); } void ParameterUpdater::startPass() { m->updater->startPass(); } From 409a5774c475b67160ea5cdf22b489652da6bff3 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 21 Dec 2016 22:45:42 +0800 Subject: [PATCH 025/119] Complete a very simple mnist demo. --- demo/mnist/api_train.py | 108 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 9 deletions(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 52cc13c5a3..c1439bd526 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -1,8 +1,17 @@ +""" +A very basic example for how to use current Raw SWIG API to train mnist network. + +Current implementation uses Raw SWIG, which means the API call is directly \ +passed to C++ side of Paddle. + +The user api could be simpler and carefully designed. +""" import py_paddle.swig_paddle as api from py_paddle import DataProviderConverter import paddle.trainer.PyDataProvider2 as dp import paddle.trainer.config_parser import numpy as np +import random from mnist_util import read_from_mnist @@ -27,6 +36,18 @@ def generator_to_batch(generator, batch_size): yield ret_val +class BatchPool(object): + def __init__(self, generator, batch_size): + self.data = list(generator) + self.batch_size = batch_size + + def __call__(self): + random.shuffle(self.data) + for offset in xrange(0, len(self.data), self.batch_size): + limit = min(offset + self.batch_size, len(self.data)) + yield self.data[offset:limit] + + def input_order_converter(generator): for each_item in generator: yield each_item['pixel'], each_item['label'] @@ -37,46 +58,115 @@ def main(): config = paddle.trainer.config_parser.parse_config( 'simple_mnist_network.py', '') + # get enable_types for each optimizer. + # enable_types = [value, gradient, momentum, etc] + # For each optimizer(SGD, Adam), GradientMachine should enable different + # buffers. opt_config = api.OptimizationConfig.createFromProto(config.opt_config) _temp_optimizer_ = api.ParameterOptimizer.create(opt_config) enable_types = _temp_optimizer_.getParameterTypes() + # Create Simple Gradient Machine. m = api.GradientMachine.createFromConfigProto( config.model_config, api.CREATE_MODE_NORMAL, enable_types) + + # This type check is not useful. Only enable type hint in IDE. + # Such as PyCharm assert isinstance(m, api.GradientMachine) + + # Initialize Parameter by numpy. init_parameter(network=m) + + # Create Local Updater. Local means not run in cluster. + # For a cluster training, here we can change to createRemoteUpdater + # in future. updater = api.ParameterUpdater.createLocalUpdater(opt_config) assert isinstance(updater, api.ParameterUpdater) + + # Initialize ParameterUpdater. updater.init(m) + # DataProvider Converter is a utility convert Python Object to Paddle C++ + # Input. The input format is as same as Paddle's DataProvider. converter = DataProviderConverter( input_types=[dp.dense_vector(784), dp.integer_value(10)]) train_file = './data/raw_data/train' + test_file = './data/raw_data/t10k' + # start gradient machine. + # the gradient machine must be started before invoke forward/backward. + # not just for training, but also for inference. m.start() - for _ in xrange(100): + # evaluator can print error rate, etc. It is a C++ class. + batch_evaluator = m.makeEvaluator() + test_evaluator = m.makeEvaluator() + + # Get Train Data. + # TrainData will stored in a data pool. Currently implementation is not care + # about memory, speed. Just a very naive implementation. + train_data_generator = input_order_converter(read_from_mnist(train_file)) + train_data = BatchPool(train_data_generator, 128) + + # outArgs is Neural Network forward result. Here is not useful, just passed + # to gradient_machine.forward + outArgs = api.Arguments.createArguments(0) + + for pass_id in xrange(2): # we train 2 passes. updater.startPass() - outArgs = api.Arguments.createArguments(0) - train_data_generator = input_order_converter( - read_from_mnist(train_file)) - for batch_id, data_batch in enumerate( - generator_to_batch(train_data_generator, 2048)): - trainRole = updater.startBatch(len(data_batch)) + for batch_id, data_batch in enumerate(train_data()): + # data_batch is input images. + # here, for online learning, we could get data_batch from network. + + # Start update one batch. + pass_type = updater.startBatch(len(data_batch)) + + # Start BatchEvaluator. + # batch_evaluator can be used between start/finish. + batch_evaluator.start() + + # A callback when backward. + # It is used for updating weight values vy calculated Gradient. def updater_callback(param): updater.update(param) + # forwardBackward is a shortcut for forward and backward. + # It is sometimes faster than invoke forward/backward separately, + # because in GradientMachine, it may be async. m.forwardBackward( - converter(data_batch), outArgs, trainRole, updater_callback) + converter(data_batch), outArgs, pass_type, updater_callback) + # Get cost. We use numpy to calculate total cost for this batch. cost_vec = outArgs.getSlotValue(0) cost_vec = cost_vec.copyToNumpyMat() cost = cost_vec.sum() / len(data_batch) - print 'Batch id', batch_id, 'with cost=', cost + + # Make evaluator works. + m.eval(batch_evaluator) + + # Print logs. + print 'Pass id', pass_id, 'Batch id', batch_id, 'with cost=', \ + cost, batch_evaluator + + batch_evaluator.finish() + # Finish batch. + # * will clear gradient. + # * ensure all values should be updated. updater.finishBatch(cost) + # testing stage. use test data set to test current network. + test_evaluator.start() + test_data_generator = input_order_converter(read_from_mnist(test_file)) + for data_batch in generator_to_batch(test_data_generator, 128): + # in testing stage, only forward is needed. + m.forward(converter(data_batch), outArgs, api.PASS_TEST) + m.eval(test_evaluator) + + # print error rate for test data set + print 'Pass', pass_id, ' test evaluator: ', test_evaluator + test_evaluator.finish() updater.finishPass() m.finish() From 680dd92bde2e4d6c2173f47d6da3263d827050e8 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Dec 2016 11:31:31 +0800 Subject: [PATCH 026/119] Add AverageOptimizer, Add save parameter --- demo/mnist/api_train.py | 13 +++++++++++++ demo/mnist/simple_mnist_network.py | 7 ++++++- paddle/api/PaddleAPI.h | 6 ++++++ paddle/api/ParameterUpdater.cpp | 6 ++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index c1439bd526..ce75d79beb 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -157,6 +157,7 @@ def main(): updater.finishBatch(cost) # testing stage. use test data set to test current network. + updater.apply() test_evaluator.start() test_data_generator = input_order_converter(read_from_mnist(test_file)) for data_batch in generator_to_batch(test_data_generator, 128): @@ -167,6 +168,18 @@ def main(): # print error rate for test data set print 'Pass', pass_id, ' test evaluator: ', test_evaluator test_evaluator.finish() + updater.restore() + + updater.catchUpWith() + params = m.getParameters() + for each_param in params: + assert isinstance(each_param, api.Parameter) + value = each_param.getBuf(api.PARAMETER_VALUE) + value = value.toNumpyArrayInplace() + + # Here, we could save parameter to every where you want + print each_param.getName(), value + updater.finishPass() m.finish() diff --git a/demo/mnist/simple_mnist_network.py b/demo/mnist/simple_mnist_network.py index 41f4e51657..f5d1ea169e 100644 --- a/demo/mnist/simple_mnist_network.py +++ b/demo/mnist/simple_mnist_network.py @@ -1,6 +1,11 @@ from paddle.trainer_config_helpers import * -settings(learning_rate=1e-4, learning_method=AdamOptimizer(), batch_size=1000) +settings( + learning_rate=1e-4, + learning_method=AdamOptimizer(), + batch_size=1000, + model_average=ModelAverage(average_window=0.5), + regularization=L2Regularization(rate=0.5)) imgs = data_layer(name='pixel', size=784) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 413c385146..d94fd1e52e 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -809,6 +809,12 @@ public: void update(Parameter* param); + void restore(); + + void apply(); + + void catchUpWith(); + private: ParameterUpdaterPrivate* m; }; diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 91c8392762..7cd8ed7e39 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -48,3 +48,9 @@ void ParameterUpdater::update(Parameter *param) { auto paddleParam = param->m->getPtr(); m->updater->update(paddleParam); } + +void ParameterUpdater::restore() { m->updater->restore(); } + +void ParameterUpdater::apply() { m->updater->apply(); } + +void ParameterUpdater::catchUpWith() { m->updater->catchUpWith(); } From 5bca268bd1f9fdc01afe52834b486119076b1e8b Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Dec 2016 14:51:51 +0800 Subject: [PATCH 027/119] Add gitignore --- demo/mnist/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/mnist/.gitignore b/demo/mnist/.gitignore index 810910fd5c..8bd9837523 100644 --- a/demo/mnist/.gitignore +++ b/demo/mnist/.gitignore @@ -4,3 +4,4 @@ mnist_vgg_model plot.png train.log *pyc +.ipynb_checkpoints From 59009ba72d54cc35717dbd80d73500f11fbb7852 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Dec 2016 14:51:51 +0800 Subject: [PATCH 028/119] Always use copy method for numpy. * Make this demo support GPU --- demo/mnist/.gitignore | 1 + demo/mnist/api_train.py | 9 ++++----- paddle/api/Paddle.swig | 1 + paddle/api/PaddleAPI.h | 2 ++ paddle/api/Parameter.cpp | 2 ++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/demo/mnist/.gitignore b/demo/mnist/.gitignore index 810910fd5c..8bd9837523 100644 --- a/demo/mnist/.gitignore +++ b/demo/mnist/.gitignore @@ -4,3 +4,4 @@ mnist_vgg_model plot.png train.log *pyc +.ipynb_checkpoints diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index ce75d79beb..7e653246a3 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -19,10 +19,9 @@ def init_parameter(network): assert isinstance(network, api.GradientMachine) for each_param in network.getParameters(): assert isinstance(each_param, api.Parameter) - array = each_param.getBuf(api.PARAMETER_VALUE).toNumpyArrayInplace() - assert isinstance(array, np.ndarray) - for i in xrange(len(array)): - array[i] = np.random.uniform(-1.0, 1.0) + array_size = len(each_param) + array = np.random.uniform(-1.0, 1.0, array_size).astype('float32') + each_param.getBuf(api.PARAMETER_VALUE).copyFromNumpyArray(array) def generator_to_batch(generator, batch_size): @@ -175,7 +174,7 @@ def main(): for each_param in params: assert isinstance(each_param, api.Parameter) value = each_param.getBuf(api.PARAMETER_VALUE) - value = value.toNumpyArrayInplace() + value = value.copyToNumpyArray() # Here, we could save parameter to every where you want print each_param.getName(), value diff --git a/paddle/api/Paddle.swig b/paddle/api/Paddle.swig index 7a110a90b8..3365927f9b 100644 --- a/paddle/api/Paddle.swig +++ b/paddle/api/Paddle.swig @@ -96,6 +96,7 @@ namespace std { %rename(__getitem__) Vector::get; %rename(__setitem__) Vector::set; %rename(__len__) Vector::getSize; +%rename(__len__) Parameter::getSize; %rename(__call__) ParameterTraverseCallback::apply; %rename(__repr__) Evaluator::toString; diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index d94fd1e52e..d4b057e8a1 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -550,6 +550,8 @@ public: ParameterConfig* getConfig(); void setValueUpdated(); + size_t getSize() const; + private: static Parameter* createFromRawPtr(void* ptr); static Parameter* createFromSharedPtr(void* ptr); diff --git a/paddle/api/Parameter.cpp b/paddle/api/Parameter.cpp index 41cf50043c..ddc00d8d1a 100644 --- a/paddle/api/Parameter.cpp +++ b/paddle/api/Parameter.cpp @@ -56,3 +56,5 @@ ParameterConfig* Parameter::getConfig() { size_t Parameter::getID() const { return m->getPtr()->getID(); } void Parameter::setValueUpdated() { m->getPtr()->setValueUpdated(); } + +size_t Parameter::getSize() const { return m->getPtr()->getSize(); } From f06b64fee47c1d807a224049243d2d3dec39fc5c Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Dec 2016 07:45:18 +0000 Subject: [PATCH 029/119] Test GPU --- demo/mnist/api_train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index ce75d79beb..e5a9075c8e 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -54,7 +54,7 @@ def input_order_converter(generator): def main(): - api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores + api.initPaddle("-use_gpu=true", "-trainer_count=4") # use 4 cpu cores config = paddle.trainer.config_parser.parse_config( 'simple_mnist_network.py', '') From 5a685841317625786d4c37eb79abfd22cec995d6 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Dec 2016 07:57:04 +0000 Subject: [PATCH 030/119] Test on GPU --- demo/mnist/api_train.py | 17 +++++++---------- paddle/api/Vector.cpp | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 129922c30b..48ba61c47d 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -53,7 +53,7 @@ def input_order_converter(generator): def main(): - api.initPaddle("-use_gpu=true", "-trainer_count=4") # use 4 cpu cores + api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores config = paddle.trainer.config_parser.parse_config( 'simple_mnist_network.py', '') @@ -106,7 +106,7 @@ def main(): # TrainData will stored in a data pool. Currently implementation is not care # about memory, speed. Just a very naive implementation. train_data_generator = input_order_converter(read_from_mnist(train_file)) - train_data = BatchPool(train_data_generator, 128) + train_data = BatchPool(train_data_generator, 512) # outArgs is Neural Network forward result. Here is not useful, just passed # to gradient_machine.forward @@ -126,16 +126,13 @@ def main(): # batch_evaluator can be used between start/finish. batch_evaluator.start() - # A callback when backward. - # It is used for updating weight values vy calculated Gradient. - def updater_callback(param): - updater.update(param) - # forwardBackward is a shortcut for forward and backward. # It is sometimes faster than invoke forward/backward separately, # because in GradientMachine, it may be async. - m.forwardBackward( - converter(data_batch), outArgs, pass_type, updater_callback) + m.forwardBackward(converter(data_batch), outArgs, pass_type) + + for each_param in m.getParameters(): + updater.update(each_param) # Get cost. We use numpy to calculate total cost for this batch. cost_vec = outArgs.getSlotValue(0) @@ -159,7 +156,7 @@ def main(): updater.apply() test_evaluator.start() test_data_generator = input_order_converter(read_from_mnist(test_file)) - for data_batch in generator_to_batch(test_data_generator, 128): + for data_batch in generator_to_batch(test_data_generator, 512): # in testing stage, only forward is needed. m.forward(converter(data_batch), outArgs, api.PASS_TEST) m.eval(test_evaluator) diff --git a/paddle/api/Vector.cpp b/paddle/api/Vector.cpp index 874f2fd044..db8f005929 100644 --- a/paddle/api/Vector.cpp +++ b/paddle/api/Vector.cpp @@ -253,7 +253,7 @@ void Vector::copyToNumpyArray(float** view_m_data, int* dim1) { *view_m_data = new float[*dim1]; if (auto cpuVec = dynamic_cast(m->vec.get())) { std::memcpy(*view_m_data, cpuVec->getData(), sizeof(float) * (*dim1)); - } else if (auto gpuVec = dynamic_cast(m->vec.get())) { + } else if (auto gpuVec = dynamic_cast(m->vec.get())) { hl_memcpy_device2host( *view_m_data, gpuVec->getData(), sizeof(float) * (*dim1)); } else { From 3a802729746468d654c1a0908a7787bc10618f94 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Dec 2016 16:57:12 +0800 Subject: [PATCH 031/119] Add comments. --- paddle/api/PaddleAPI.h | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index d4b057e8a1..0a273f9f6f 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -799,22 +799,61 @@ public: static ParameterUpdater* createLocalUpdater(OptimizationConfig* config); ~ParameterUpdater(); + /** + * @brief initialize Parameter Updater by GradientMachine. + * @param gm + */ void init(const GradientMachine& gm); + /** + * @brief begin of a training/testing of one pass. + */ void startPass(); + /** + * @brief end of a traning/testing of one pass. + */ void finishPass(); + /** + * @brief begin of a training/testing of one batch. + * @param data batch's size + * @return PassType, mostly will be training. + */ PassType startBatch(size_t batchSize); + /** + * @brief end of a traning/testing of one batch + * @param cost current batch cost. + */ void finishBatch(float cost); + /** + * @brief update a parameter (by local optimizer or by cluster pserver) + * @param param + */ void update(Parameter* param); + /** + * @brief restore the average parameter. + * @note It is only used in AverageOptimizer. Restore will get the current + * PARAMETER_VALUE back. + */ void restore(); + /** + * @brief apply. Store the average parameter. + * @note It is only used in AverageOptimizer. Apply will store the current + * PARAMETER_VALUE to buffer, calcaualte current Average Parameter, and save + * it to PARAMETER_VALUE. + */ void apply(); + /** + * @brief catchUpWith The Regularization will be delayed in many situations( + * pserver, local sparse). Catch Up means catch the regularization up, apply + * regularization to all params. + */ void catchUpWith(); private: @@ -830,10 +869,21 @@ private: public: ~Evaluator(); + /** + * @brief begin an evaluate stage. + */ void start(); + /** + * @brief end an evaluate stage. + */ void finish(); + /** + * @brief toString will get a evaluate result. + * + * __repr__ method in python + */ std::string toString(); private: From dcc0671c13a510469332b48e108a86ca12b77210 Mon Sep 17 00:00:00 2001 From: zhouti Date: Thu, 22 Dec 2016 19:17:14 +0800 Subject: [PATCH 032/119] Modified VM OS from Debian to CoreOS in doc. --- doc/howto/usage/cluster/k8s-aws/README.md | 491 +++++++++++----------- 1 file changed, 245 insertions(+), 246 deletions(-) diff --git a/doc/howto/usage/cluster/k8s-aws/README.md b/doc/howto/usage/cluster/k8s-aws/README.md index 011dfa4064..5931584288 100644 --- a/doc/howto/usage/cluster/k8s-aws/README.md +++ b/doc/howto/usage/cluster/k8s-aws/README.md @@ -45,7 +45,64 @@ it. Here we will show you step by step on how to run PaddlePaddle training on AWS cluster. -###AWS Login + +###Download kube-aws and kubectl + +####kube-aws + +Import the CoreOS Application Signing Public Key: + +``` +gpg2 --keyserver pgp.mit.edu --recv-key FC8A365E +``` + +Validate the key fingerprint: + +``` +gpg2 --fingerprint FC8A365E +``` +The correct key fingerprint is `18AD 5014 C99E F7E3 BA5F 6CE9 50BD D3E0 FC8A 365E` + +Go to the [releases](https://github.com/coreos/kube-aws/releases) and download the latest release tarball and detached signature (.sig) for your architecture. + +Validate the tarball's GPG signature: + +``` +PLATFORM=linux-amd64 + # Or +PLATFORM=darwin-amd64 + +gpg2 --verify kube-aws-${PLATFORM}.tar.gz.sig kube-aws-${PLATFORM}.tar.gz +``` + +Extract the binary: + +``` +tar zxvf kube-aws-${PLATFORM}.tar.gz +``` + +Add kube-aws to your path: + +``` +mv ${PLATFORM}/kube-aws /usr/local/bin +``` + + +####kubectl + +Go to the [releases](https://github.com/kubernetes/kubernetes/releases) and download the latest release tarball. + +Extract the tarball and then concate the kubernetes binaries directory into PATH: + +``` +export PATH=/platforms/linux/amd64:$PATH + +``` + +User credentials and security tokens will be generated later in user directory, not in `~/.kube/config`, they will be necessary to use the CLI or the HTTP Basic Auth. + + +###Configure AWS Credentials First check out [this](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) for installing the AWS command line interface, if you use ec2 instance with default amazon AMI, the cli tool has already been installed on your machine. @@ -58,7 +115,7 @@ aws configure ``` -Fill in the required fields (You can get your AWS aceess key id and AWS secrete access key by following [this](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html) instruction): +Fill in the required fields (You can get your AWS aceess key id and AWS secrete access key by following [this](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) instruction): ``` @@ -69,194 +126,190 @@ Default output format: json ``` -###Kubernetes Cluster Start Up +Test that your credentials work by describing any instances you may already have running on your account: +``` +aws ec2 describe-instances +``` + +###Define Cluster Parameters + +####EC2 key pair + +The keypair that will authenticate SSH access to your EC2 instances. The public half of this key pair will be configured on each CoreOS node. + +After creating a key pair, you will use the name you gave the keys to configure the cluster. Key pairs are only available to EC2 instances in the same region. More info in the [EC2 Keypair docs](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html). + +####KMS key -And then execute the following command after your aws login: - -``` -export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash - -``` - -By default, this command will download and unzip the latest Kubernetes release package and execute the script inside to provision a new VPC (virtual private cloud) and a four t2.micro node cluster in us-west-2a (Oregon) under that VPC. - - -You can override the variables defined in `/cluster/config-default.sh` as follows: - -``` -export KUBE_AWS_ZONE=us-west-2a -export NUM_NODES=3 -export MASTER_SIZE=m3.medium -export NODE_SIZE=m3.large -export AWS_S3_REGION=us-west-2a -export AWS_S3_BUCKET=mycompany-kubernetes-artifacts -export KUBE_AWS_INSTANCE_PREFIX=k8s - -``` - - -This process takes about 5 to 10 minutes. - -``` -[ec2-user@ip-172-31-27-229 ~]$ export KUBERNETES_PROVIDER=aws; curl -sS https://get.k8s.io | bash -'kubernetes' directory already exist. Should we skip download step and start to create cluster based on it? [Y]/n -Skipping download step. -Creating a kubernetes on aws... -... Starting cluster in us-west-2a using provider aws -... calling verify-prereqs -... calling kube-up -Starting cluster using os distro: jessie -Uploading to Amazon S3 -+++ Staging server tars to S3 Storage: kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel -upload: ../../../tmp/kubernetes.KsacFg/s3/bootstrap-script to s3://kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel/bootstrap-script -Uploaded server tars: - SERVER_BINARY_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel/kubernetes-server-linux-amd64.tar.gz - SALT_TAR_URL: https://s3.amazonaws.com/kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel/kubernetes-salt.tar.gz - BOOTSTRAP_SCRIPT_URL: https://s3.amazonaws.com/kubernetes-staging-9996f910edd9ec30ed3f8e3a9db7466c/devel/bootstrap-script -INSTANCEPROFILE arn:aws:iam::330323714104:instance-profile/kubernetes-master 2016-12-01T03:19:54Z AIPAIQDDLSMLWJ2QDXM6I kubernetes-master / -ROLES arn:aws:iam::330323714104:role/kubernetes-master 2016-12-01T03:19:52Z / AROAJDKKDIYHJTTEJM73M kubernetes-master -ASSUMEROLEPOLICYDOCUMENT 2012-10-17 -STATEMENT sts:AssumeRole Allow -PRINCIPAL ec2.amazonaws.com -INSTANCEPROFILE arn:aws:iam::330323714104:instance-profile/kubernetes-minion 2016-12-01T03:19:57Z AIPAJGNG4GYTNVP3UQU4S kubernetes-minion / -ROLES arn:aws:iam::330323714104:role/kubernetes-minion 2016-12-01T03:19:55Z / AROAIZVAWWBIVUENE5XB4 kubernetes-minion -ASSUMEROLEPOLICYDOCUMENT 2012-10-17 -STATEMENT sts:AssumeRole Allow -PRINCIPAL ec2.amazonaws.com -Using SSH key with (AWS) fingerprint: 70:66:c6:3d:53:3b:e5:3d:1d:7f:cd:c9:d1:87:35:81 -Creating vpc. -Adding tag to vpc-e01fc087: Name=kubernetes-vpc -Adding tag to vpc-e01fc087: KubernetesCluster=kubernetes -Using VPC vpc-e01fc087 -Adding tag to dopt-807151e4: Name=kubernetes-dhcp-option-set -Adding tag to dopt-807151e4: KubernetesCluster=kubernetes -Using DHCP option set dopt-807151e4 -Creating subnet. -Adding tag to subnet-4a9a642d: KubernetesCluster=kubernetes -Using subnet subnet-4a9a642d -Creating Internet Gateway. -Using Internet Gateway igw-821a73e6 -Associating route table. -Creating route table -Adding tag to rtb-0d96fa6a: KubernetesCluster=kubernetes -Associating route table rtb-0d96fa6a to subnet subnet-4a9a642d -Adding route to route table rtb-0d96fa6a -Using Route Table rtb-0d96fa6a -Creating master security group. -Creating security group kubernetes-master-kubernetes. -Adding tag to sg-a47564dd: KubernetesCluster=kubernetes -Creating minion security group. -Creating security group kubernetes-minion-kubernetes. -Adding tag to sg-9a7564e3: KubernetesCluster=kubernetes -Using master security group: kubernetes-master-kubernetes sg-a47564dd -Using minion security group: kubernetes-minion-kubernetes sg-9a7564e3 -Creating master disk: size 20GB, type gp2 -Adding tag to vol-0eba023cc1874c790: Name=kubernetes-master-pd -Adding tag to vol-0eba023cc1874c790: KubernetesCluster=kubernetes -Allocated Elastic IP for master: 35.165.155.60 -Adding tag to vol-0eba023cc1874c790: kubernetes.io/master-ip=35.165.155.60 -Generating certs for alternate-names: IP:35.165.155.60,IP:172.20.0.9,IP:10.0.0.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,DNS:kubernetes-master -Starting Master -Adding tag to i-097f358631739e01c: Name=kubernetes-master -Adding tag to i-097f358631739e01c: Role=kubernetes-master -Adding tag to i-097f358631739e01c: KubernetesCluster=kubernetes -Waiting for master to be ready -Attempt 1 to check for master nodeWaiting for instance i-097f358631739e01c to be running (currently pending) -Sleeping for 3 seconds... -Waiting for instance i-097f358631739e01c to be running (currently pending) -Sleeping for 3 seconds... -Waiting for instance i-097f358631739e01c to be running (currently pending) -Sleeping for 3 seconds... -Waiting for instance i-097f358631739e01c to be running (currently pending) -Sleeping for 3 seconds... - [master running] -Attaching IP 35.165.155.60 to instance i-097f358631739e01c -Attaching persistent data volume (vol-0eba023cc1874c790) to master -2016-12-13T10:56:50.378Z /dev/sdb i-097f358631739e01c attaching vol-0eba023cc1874c790 -cluster "aws_kubernetes" set. -user "aws_kubernetes" set. -context "aws_kubernetes" set. -switched to context "aws_kubernetes". -user "aws_kubernetes-basic-auth" set. -Wrote config for aws_kubernetes to /home/ec2-user/.kube/config -Creating minion configuration -Creating autoscaling group - 0 minions started; waiting - 0 minions started; waiting - 0 minions started; waiting - 0 minions started; waiting - 0 minions started; waiting - 3 minions started; ready -Waiting for cluster initialization. - - This will continually check to see if the API for kubernetes is reachable. - This might loop forever if there was some uncaught error during start - up. - -...........................................................................................................................................................................Kubernetes cluster created. -Sanity checking cluster... -Attempt 1 to check Docker on node @ 35.165.35.181 ...working -Attempt 1 to check Docker on node @ 35.165.79.208 ...working -Attempt 1 to check Docker on node @ 35.163.90.67 ...working - -Kubernetes cluster is running. The master is running at: - - https://35.165.155.60 - -The user name and password to use is located in /home/ec2-user/.kube/config. - -... calling validate-cluster -Waiting for 3 ready nodes. 0 ready nodes, 3 registered. Retrying. -Waiting for 3 ready nodes. 0 ready nodes, 3 registered. Retrying. -Found 3 node(s). -NAME STATUS AGE -ip-172-20-0-186.us-west-2.compute.internal Ready 33s -ip-172-20-0-187.us-west-2.compute.internal Ready 34s -ip-172-20-0-188.us-west-2.compute.internal Ready 34s -Validate output: -NAME STATUS MESSAGE ERROR -scheduler Healthy ok -controller-manager Healthy ok -etcd-1 Healthy {"health": "true"} -etcd-0 Healthy {"health": "true"} -Cluster validation succeeded -Done, listing cluster services: - -Kubernetes master is running at https://35.165.155.60 -Elasticsearch is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/elasticsearch-logging -Heapster is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/heapster -Kibana is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/kibana-logging -KubeDNS is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/kube-dns -kubernetes-dashboard is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard -Grafana is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana -InfluxDB is running at https://35.165.155.60/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb - -To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. - -Kubernetes binaries at /home/ec2-user/kubernetes/cluster/ -You may want to add this directory to your PATH in $HOME/.profile -Installation successful! -``` - - -Once the cluster is up, the IP addresses of your master and node(s) will be printed, as well as information about the default services running in the cluster (monitoring, logging, dns). - -User credentials and security tokens are written in `~/.kube/config`, they will be necessary to use the CLI or the HTTP Basic Auth. - - - -And then concate the kubernetes binaries directory into PATH: +Amazon KMS keys are used to encrypt and decrypt cluster TLS assets. If you already have a KMS Key that you would like to use, you can skip creating a new key and provide the Arn string for your existing key. +You can create a KMS key in the AWS console, or with the aws command line tool: ``` -export PATH=/platforms/linux/amd64:$PATH +$ aws kms --region=us-west-2 create-key --description="kube-aws assets" +{ + "KeyMetadata": { + "CreationDate": 1458235139.724, + "KeyState": "Enabled", + "Arn": "arn:aws:kms:us-west-2:xxxxxxxxx:key/xxxxxxxxxxxxxxxxxxx", + "AWSAccountId": "xxxxxxxxxxxxx", + "Enabled": true, + "KeyUsage": "ENCRYPT_DECRYPT", + "KeyId": "xxxxxxxxx", + "Description": "kube-aws assets" + } +} +``` + +You will use the `KeyMetadata.Arn` string to identify your KMS key in the init step. +And then you need to add several inline policies in your user permission. + +kms inline policy: + +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Stmt1482205552000", + "Effect": "Allow", + "Action": [ + "kms:Decrypt", + "kms:Encrypt" + ], + "Resource": [ + "arn:aws:kms:*:xxxxxxxxx:key/*" + ] + } + ] +} ``` +cloudformation inline policy: +``` +"Version": "2012-10-17", + "Statement": [ + { + "Sid": "Stmt1482205746000", + "Effect": "Allow", + "Action": [ + "cloudformation:CreateStack", + "cloudformation:UpdateStack", + "cloudformation:DeleteStack", + "cloudformation:DescribeStacks", + "cloudformation:DescribeStackResource", + "cloudformation:GetTemplate" + ], + "Resource": [ + "arn:aws:cloudformation:us-west-2:xxxxxxxxx:stack/YOUR_CLUSTER_NAME/*" + ] + } + ] +} +``` + + +####External DNS name + +When the cluster is created, the controller will expose the TLS-secured API on a public IP address. You will need to create an A record for the external DNS hostname you want to point to this IP address. You can find the API external IP address after the cluster is created by invoking kube-aws status. + +####S3 bucket + +You need to create an S3 bucket before startup the Kubernetes cluster. -Now you can use Kubernetes administration tool `kubectl` to operate the cluster, let's give `kubectl get nodes` a try. +####Initialize an asset directory +Create a directory on your local machine to hold the generated assets: + +``` +$ mkdir my-cluster +$ cd my-cluster +``` + +Initialize the cluster CloudFormation stack with the KMS Arn, key pair name, and DNS name from the previous step: + +``` +$ kube-aws init \ +--cluster-name=my-cluster-name \ +--external-dns-name=my-cluster-endpoint \ +--region=us-west-1 \ +--availability-zone=us-west-1c \ +--key-name=key-pair-name \ +--kms-key-arn="arn:aws:kms:us-west-2:xxxxxxxxxx:key/xxxxxxxxxxxxxxxxxxx" +``` + +There will now be a cluster.yaml file in the asset directory. This is the main configuration file for your cluster. + +####Render contents of the asset directory + +In the simplest case, you can have kube-aws generate both your TLS identities and certificate authority for you. + +``` +$ kube-aws render credentials --generate-ca +``` + +The next command generates the default set of cluster assets in your asset directory. + +``` +sh $ kube-aws render stack +``` + +Here's what the directory structure looks like: + +``` +$ tree +. +├── cluster.yaml +├── credentials +│ ├── admin-key.pem +│ ├── admin.pem +│ ├── apiserver-key.pem +│ ├── apiserver.pem +│ ├── ca-key.pem +│ ├── ca.pem +│ ├── worker-key.pem +│ └── worker.pem +│ ├── etcd-key.pem +│ └── etcd.pem +│ ├── etcd-client-key.pem +│ └── etcd-client.pem +├── kubeconfig +├── stack-template.json +└── userdata + ├── cloud-config-controller + └── cloud-config-worker +``` + +These assets (templates and credentials) are used to create, update and interact with your Kubernetes cluster. + + +###Kubernetes Cluster Start Up + +####Create the instances defined in the CloudFormation template + +Now for the exciting part, creating your cluster: + +``` +$ kube-aws up --s3-uri s3:/// +``` + +####Configure DNS + +You can invoke `kube-aws status` to get the cluster API endpoint after cluster creation, if necessary. This command can take a while. And then dig the load balancer hostname to get the ip address, use this ip to setup an A record for your external dns name. + +####Access the cluster + +Once the API server is running, you should see: + +``` +$ kubectl --kubeconfig=kubeconfig get nodes +NAME STATUS AGE +ip-10-0-0-xxx.us-west-1.compute.internal Ready 5m +ip-10-0-0-xxx.us-west-1.compute.internal Ready 5m +ip-10-0-0-xx.us-west-1.compute.internal Ready,SchedulingDisabled 5m +``` ###Setup PaddlePaddle Environment on AWS @@ -277,7 +330,7 @@ For sharing the training data across all the Kubernetes nodes, we use EFS (Elast 1. Make sure you added AmazonElasticFileSystemFullAccess policy in your group. -1. Create the Elastic File System in AWS console, and attach the Kubernetes VPC with it. +1. Create the Elastic File System in AWS console, and attach the new VPC with it. @@ -295,7 +348,7 @@ Before starting the training, you should place your user config and divided trai ###Core Concept of PaddlePaddle Training on AWS -Now we've already setup a 3 node distributed training cluster, and on each node we've attached the EFS volume, in this training demo, we will create three Kubernetes pod and scheduling them on 3 node. Each pod contains a PaddlePaddle container. When container gets created, it will start pserver and trainer process, load the training data from EFS volume and start the distributed training task. +Now we've already setup a 3 nodes distributed Kubernetes cluster, and on each node we've attached the EFS volume, in this training demo, we will create three Kubernetes pod and scheduling them on 3 node. Each pod contains a PaddlePaddle container. When container gets created, it will start pserver and trainer process, load the training data from EFS volume and start the distributed training task. ####Use Kubernetes Job @@ -307,7 +360,7 @@ In one time of distributed training, user will confirm the PaddlePaddle node num ####Create PaddlePaddle Node -After Kubernetes master gets the request, it will parse the yaml file and create several pods (PaddlePaddle's node number), Kubernetes will allocate these pods onto cluster's node. A pod represents a PaddlePaddle node, when pod is successfully allocated onto one physical/virtual machine, Kubernetes will startup the container in the pod, and this container will use the environment variables in yaml file and start up `paddle pserver` and `paddle trainer` processes. +After Kubernetes master gets the request, it will parse the yaml file and create several pods (defined by PaddlePaddle's node number), Kubernetes will allocate these pods onto cluster's node. A pod represents a PaddlePaddle node, when pod is successfully allocated onto one physical/virtual machine, Kubernetes will startup the container in the pod, and this container will use the environment variables in yaml file and start up `paddle pserver` and `paddle trainer` processes. ####Start up Training @@ -584,7 +637,7 @@ I1116 09:10:18.019716 50 ParameterClient2.cpp:122] pserver 4 192.168.129.71:7 I1116 09:10:18.019836 50 ParameterClient2.cpp:122] pserver 5 192.168.129.71:7165 ``` -It'll take around 8 hours to run this PaddlePaddle recommendation training demo on three 2 core 8 GB EC2 machine (m3.large), and the results will be 10 trained models. +It'll take around 8 hours to finish this PaddlePaddle recommendation training demo on three 2 core 8 GB EC2 machine (m3.large). ###Kubernetes Cluster Tear Down @@ -592,51 +645,13 @@ It'll take around 8 hours to run this PaddlePaddle recommendation training demo If you want to tear down the whole Kubernetes cluster, make sure to *delete* the EFS volume first (otherwise, you will get stucked on following steps), and then use the following command: - ``` -export KUBERNETES_PROVIDER=aws; /cluster/kube-down.sh -``` - -This process takes about 2 to 5 minutes. - -``` -ec2-user@ip-172-31-27-229 ~]$ export KUBERNETES_PROVIDER=aws; ./kubernetes/cluster/kube-down.sh -Bringing down cluster using provider: aws -Deleting instances in VPC: vpc-e01fc087 -Deleting auto-scaling group: kubernetes-minion-group-us-west-2a -Deleting auto-scaling launch configuration: kubernetes-minion-group-us-west-2a -Deleting auto-scaling group: kubernetes-minion-group-us-west-2a -Deleting auto-scaling group: kubernetes-minion-group-us-west-2a -Waiting for instances to be deleted -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -Waiting for instance i-04e973f1d6d56d580 to be terminated (currently shutting-down) -Sleeping for 3 seconds... -All instances deleted -Releasing Elastic IP: 35.165.155.60 -Deleting volume vol-0eba023cc1874c790 -Cleaning up resources in VPC: vpc-e01fc087 -Cleaning up security group: sg-9a7564e3 -Cleaning up security group: sg-a47564dd -Deleting security group: sg-9a7564e3 -Deleting security group: sg-a47564dd -Deleting VPC: vpc-e01fc087 -Done +kube-aws destroy ``` +It's an async call, it might take 5 min to tear down the whole cluster. + +If you created any Kubernetes Services of type LoadBalancer, you must delete these first, as the CloudFormation cannot be fully destroyed if any externally-managed resources still exist. + ## For Experts with Kubernetes and AWS @@ -645,23 +660,7 @@ Sometimes we might need to create or manage the cluster on AWS manually with lim ### Some Presumptions -* Instances run on Debian, the official IAM, and the filesystem is aufs instead of ext4. -* Kubernetes node use instance storage, no EBS get mounted. Master use a persistent volume for etcd. -* Nodes are running in an Auto Scaling Group on AWS, auto-scaling itself is disabled, but if some node get terminated, it will launch another node instead. -* For networking, we use ip-per-pod model here, each pod get assigned a /24 CIDR. And the whole vpc is a /16 CIDR, No overlay network at this moment, we will use Calico solution later on. +* Instances run on CoreOS, the official IAM. +* Kubernetes node use instance storage, no EBS get mounted. Etcd is running on additional node. +* For networking, we use Flannel network at this moment, we will use Calico solution later on. * When you create a service with Type=LoadBalancer, Kubernetes will create and ELB, and create a security group for the ELB. -* Kube-proxy sets up two IAM roles, one for master called kubernetes-master, one for nodes called kubernetes-node. -* All AWS resources are tagged with a tag named "KubernetesCluster", with a value that is the unique cluster-id. - - -###Script Detailed Steps - -* Create an s3 bucket for binaries and scripts. -* Create two iam roles: kubernetes-master, kubernetes-node. -* Create an AWS SSH key named kubernetes-YOUR_RSA_FINGERPRINT. -* Create a vpc with 172.20.0.0/16 CIDR, and enables dns-support and dns-hostnames options in vpc settings. -* Create Internet gateway, route table, a subnet with CIDR of 172.20.0.0/24, and associate the subnet to the route table. -* Create and configure security group for master and nodes. -* Create an EBS for master, it will be attached after the master node get up. -* Launch the master with fixed ip address 172.20.0.9, and the node is initialized with Salt script, all the components get started as docker containers. -* Create an auto-scaling group, it has the min and max size, it can be changed by using aws api or console, it will auto launch the kubernetes node and configure itself, connect to master, assign an internal CIDR, and the master configures the route table with the assigned CIDR. From c63358ba124d77b1a13f3f0e84371f6ab74f8ee3 Mon Sep 17 00:00:00 2001 From: lifu Date: Thu, 22 Dec 2016 20:16:52 +0800 Subject: [PATCH 033/119] add translation for cmd_parameter --- doc/howto/usage/cmd_parameter/arguments_cn.md | 409 ++++++++++++++++++ .../cmd_parameter/detail_introduction_cn.md | 336 ++++++++++++++ .../cmd_parameter/detail_introduction_en.md | 6 +- doc/howto/usage/cmd_parameter/index_en.md | 5 +- doc/howto/usage/cmd_parameter/use_case_cn.md | 182 ++++++++ doc/howto/usage/cmd_parameter/use_case_en.md | 20 +- 6 files changed, 939 insertions(+), 19 deletions(-) create mode 100644 doc/howto/usage/cmd_parameter/arguments_cn.md create mode 100644 doc/howto/usage/cmd_parameter/detail_introduction_cn.md create mode 100644 doc/howto/usage/cmd_parameter/use_case_cn.md diff --git a/doc/howto/usage/cmd_parameter/arguments_cn.md b/doc/howto/usage/cmd_parameter/arguments_cn.md new file mode 100644 index 0000000000..777cac7f93 --- /dev/null +++ b/doc/howto/usage/cmd_parameter/arguments_cn.md @@ -0,0 +1,409 @@ +# 参数概述 + +虽然Paddle看起来包含了众多参数,但是大部分参数是为开发者提供的,或者已经在集群提交环境中自动设置,因此用户并不需要关心它们。在此,根据这些参数的使用场合,我们将它们划分为不同的类别。例如,`通用`类别中的参数可用于所有场合。某些参数只可用于特定的层中,而有些参数需要在集群多机训练中使用等。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +√ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数本地训练集群训练本地测试集群测试
通用job
use_gpu
local
config
config_args
num_passes
trainer_count
version
show_layer_stat
训练dot_period
test_period
saving_period
show_parameter_stats_period
init_model_path
load_missing_parameter_strategy
saving_period_by_batches
use_old_updater
enable_grad_share
grad_share_block_num
log_error_clipping
log_clipping
save_only_one
allow_inefficient_sparse_update
start_pass
训练/测试save_dir
训练过程中测试test_period
average_test_period
测试model_list
test_wait
test_pass
predict_output_dir
distribute_test
Auc/正负对验证(PnpairValidation)predict_file
GPUgpu_id
parallel_nn
allow_only_one_model_on_one_gpu
cudnn_dir
cuda_dir
cudnn_conv_workspace_limit_in_mb
递归神经网络(RNN)beam_size
rnn_use_batch
prev_batch_state
diy_beam_search_prob_so
度量学习(metric learning)external
data_server_port
参数服务(PServer)start_pserver
pservers
port
port_num
ports_num_for_sparse
nics
rdma_tcp
small_messages
loadsave_parameters_in_pserver
log_period_server
pserver_num_threads
sock_send_buf_size
sock_recv_buf_size
num_gradient_servers
parameter_block_size
parameter_block_size_for_sparse
异步随机梯度下降(Async SGD)async_count
async_lagged_ratio_min
async_lagged_ratio_default
性能调优(Performance Tuning)log_barrier_abstract
log_barrier_lowest_nodes
log_barrier_show_log
check_sparse_distribution_batches
check_sparse_distribution_ratio
check_sparse_distribution_unbalance_degree
check_sparse_distribution_in_pserver
show_check_sparse_distribution_log
数据提供(Data Provider)memory_threshold_on_load_data
随机数seed
thread_local_rand_use_global_seed
单元测试checkgrad_eps
矩阵/向量enable_parallel_vector
+ diff --git a/doc/howto/usage/cmd_parameter/detail_introduction_cn.md b/doc/howto/usage/cmd_parameter/detail_introduction_cn.md new file mode 100644 index 0000000000..1e6f32eb49 --- /dev/null +++ b/doc/howto/usage/cmd_parameter/detail_introduction_cn.md @@ -0,0 +1,336 @@ +# 细节描述 + +## 通用 + +* `--job` + - 工作模式,包括: **train, test, checkgrad**,其中checkgrad主要为开发者使用,使用者不需要关心。 + - 类型: string (默认: train) + +* `--config` + - 用于指定网络配置文件。 + - 类型: string (默认: null). + +* `--use_gpu` + - 训练过程是否使用GPU,设置为true使用GPU模式,否则使用CPU模式。 + - 类型: bool (默认: 1). + +* `--local` + - 训练过程是否为本地模式,设置为true使用本地训练或者使用集群上的一个节点,否则使用集群上的多台机器。 + - 类型: bool (默认: 1). + +* `--trainer_count` + - 指定一台机器上使用的线程数。例如,trainer_count = 4, 意思是在GPU模式下使用4个GPU,或者在CPU模式下使用4个线程。每个线程(或GPU)分配到当前数据块样本数的四分之一。也就是说,如果在训练配置中设置batch_size为512,每个线程分配到128个样本用于训练。 + - 类型: int32 (默认: 1). + +* `--num_passes` + - 当模式为`--job=train`时, 该参数的意思是训练num_passes轮。每轮会将数据集中的所有训练样本使用一次。当模式为`--job=test`时,意思是使用第test_pass个模型到第 num_passes-1 个模型测试数据。 + - 类型: int32 (默认: 100). + +* `--config_args` + - 传递给配置文件的参数。格式: key1=value1,key2=value2. + - 类型: string (默认: null). + +* `--version` + - 是否打印版本信息。 + - 类型: bool (默认: 0). + +* `--show_layer_stat` + - 是否显示**每个批次数据**中每层的数值统计. + - 类型: bool (默认: 0). + +## 训练 + +* `--log_period` + - 每log_period个批次打印日志进度. + - 类型: int32 (默认: 100). + +* `--dot_period` + - 每dot_period个批次输出符号'.'. + - 类型: int32 (默认: 1). + +* `--saving_period` + - 每saving_period轮保存训练参数. + - 类型: int32 (默认: 1). + +* `--save_dir` + - 保存模型参数的目录,需要明确指定,但不需要提前创建。 + - 类型: string (默认: null). + +* `--start_pass` + - 从start_pass轮开始训练,会加载上一轮的参数。 + - 类型: int32 (默认: 0). + +* `--show_parameter_stats_period` + - 在训练过程中每show_parameter_stats_period个批次输出参数统计。默认不显示。 + - 类型: int32 (默认: 0). + +* `--save_only_one` + - 只保存最后一轮的参数,而之前的参数将会被删除。 + - 类型: bool (默认: 0). + +* `--load_missing_parameter_strategy` + - 当模型参数不存在时,指定加载的方式。目前支持fail/rand/zero三种操作. + - `fail`: 程序直接退出. + - `rand`: 根据网络配置中的**initial\_strategy**采用均匀分布或者高斯分布初始化。均匀分布的范围是: **[mean - std, mean + std]**, 其中mean和std是训练配置中的参数. + - `zero`: 所有参数置为零. + - 类型: string (默认: fail). + +* `--init_model_path` + - 初始化模型的路径。如果设置该参数,start\_pass将不起作用。同样也可以在测试模式中指定模型路径。 + - 类型: string (默认: null). + +* `--saving_period_by_batches` + - 在一轮中每saving_period_by_batches个批次保存一次参数。 + - 类型: int32 (默认: 0). + +* `--log_error_clipping` + - 当在网络层配置中设置**error_clipping_threshold**时,该参数指示是否打印错误截断日志。如果为true,**每批次**的反向传播将会打印日志信息。该截断会影响**输出的梯度**. + - 类型: bool (默认: 0). + +* `--log_clipping` + - 当在训练配置中设置**gradient_clipping_threshold**时,该参数指示是否打印日志截断信息。该截断会影响**权重更新的梯度**. + - 类型: bool (默认: 0). + +* `--use_old_updater` + - 是否使用旧的RemoteParameterUpdater。 默认使用ConcurrentRemoteParameterUpdater,主要为开发者使用,使用者通常无需关心. + - 类型: bool (默认: 0). + +* `--enable_grad_share` + - 启用梯度参数的阈值,在多CPU训练时共享该参数. + - 类型: int32 (默认: 100 \* 1024 \* 1024). + +* `--grad_share_block_num` + - 梯度参数的分块数目,在多CPU训练时共享该参数. + - 类型: int32 (默认: 64). + +## 测试 + +* `--test_pass` + - 加载test_pass轮的模型用于测试. + - 类型: int32 (默认: -1). + +* `--test_period` + - 如果为0,每轮结束时对所有测试数据进行测试;如果不为0,每test_period个批次对所有测试数据进行测试. + - 类型: int32 (默认: 0). + +* `--test_wait` + - 指示当指定轮的测试模型不存在时,是否需要等待该轮模型参数。如果在集群的提交环境中设置了test_data_path,将会启动一个进程执行测试,因此我们需要设置test_wait=1。需要注意的是,在集群提交环境中,该参数已经默认设置为True. + - 类型: bool (默认: 0). + +* `--model_list` + - 测试时指定的存储模型列表的文件。在集群提交环境中,在指定model_path之后,该参数会自动设置. + - 类型: string (默认: "", null). + +* `--predict_output_dir` + - 保存网络层输出结果的目录。该参数在网络配置的Outputs()中指定,默认为null,意思是不保存结果。在测试阶段,如果你想要保存某些层的特征图,请指定该目录。需要注意的是,网络层的输出是经过激活函数之后的值. + - 类型: string (默认: "", null). + +* `--average_test_period` + - 使用`average_test_period`个批次的参数平均值进行测试。该参数必须能被FLAGS_log_period整除,默认为0,意思是不使用平均参数执行测试. + - 类型: int32 (默认: 0). + +* `--distribute_test` + - 在分布式环境中测试,将多台机器的测试结果合并. + - 类型: bool (默认: 0). + +* `--predict_file` + - 保存预测结果的文件名。该参数默认为null,意思是不保存结果。目前该参数仅用于AucValidationLayer和PnpairValidationLayer层,每轮都会保存预测结果. + - 类型: string (默认: "", null). + +## GPU + +* `--gpu_id` + - 指示使用哪个GPU核. + - 类型: int32 (默认: 0). + +* `--allow_only_one_model_on_one_gpu` + - 如果为true,一个GPU设备上不允许配置多个模型. + - 类型: bool (默认: 1). + +* `--parallel_nn` + - 指示是否使用多线程来计算一个神经网络。如果为false,设置gpu_id指定使用哪个GPU核(训练配置中的设备属性将会无效)。如果为true,GPU核在训练配置中指定(gpu_id无效). + - 类型: bool (默认: 0). + +* `--cudnn_dir` + - 选择路径来动态加载NVIDIA CuDNN库,例如,/usr/local/cuda/lib64. [默认]: LD_LIBRARY_PATH + - 类型: string (默认: "", null) + +* `--cuda_dir` + - 选择路径来动态加载NVIDIA CUDA库,例如,/usr/local/cuda/lib64. [默认]: LD_LIBRARY_PATH + - 类型: string (默认: "", null) + +* `--cudnn_conv_workspace_limit_in_mb` + - 指定cuDNN的最大工作空间容限,单位是MB,默认为4096MB=4GB. + - 类型: int32 (默认: 4096MB=4GB) + +## 自然语言处理(NLP): RNN/LSTM/GRU +* `--rnn_use_batch` + - 指示在简单的RecurrentLayer层的计算中是否使用批处理方法. + - 类型: bool (默认: 0). + +* `--prev_batch_state` + - 指示批次之间是否是连续的. + - 类型: bool (默认: 0). + +* `--beam_size` + - 集束搜索使用广度优先搜索的方式构建查找树。在树的每一层上,都会产生当前层状态的所有继承结果,按启发式损失的大小递增排序。然而,每层上只能保存固定数目个最好的状态,该数目是提前定义好的,称之为集束大小. + - 类型: int32 (默认: 1). + +* `--diy_beam_search_prob_so` + - 指定共享的动态库,用户可以定义Paddle之外的文件. + - 类型: string (默认: "", null). + +## 度量学习(Metric Learning) +* `--external` + - 指示是否使用外部机器进行度量学习. + - 类型: bool (默认: 0). + +* `--data_server_port` + - 数据服务器(data server)的监听端口,主要用在度量学习中. + - 类型: int32 (默认: 21134). + +## 数据支持(DataProvider) + +* `--memory_threshold_on_load_data` + - 内存容限阈值,当超过该阈值时,停止加载数据. + - 类型: double (默认: 1.0). + +## Unit Test + +* `--checkgrad_eps` + - 使用checkgrad模式时的参数变化大小. + - 类型: double (默认: 1e-05). + +## 参数服务器(Parameter Server)和分布式通信(Distributed Communication) + +* `--start_pserver` + - 指示是否开启参数服务器(parameter server). + - 类型: bool (默认: 0). + +* `--pservers` + - 参数服务器的IP地址,以逗号间隔,在集群提交环境中自动设置. + - 类型: string (默认: "127.0.0.1"). + +* `--port` + - 参数服务器的监听端口. + - 类型: int32 (默认: 20134). + +* `--ports_num` + - 发送参数的端口号,根据默认端口号递增. + - 类型: int32 (默认: 1). + +* `--trainer_id` + - 在分布式训练中,每个训练器必须指定一个唯一的id号,从0到num_trainers-1。0号训练器是主训练器。使用者无需关心这个参数. + - 类型: int32 (默认: 0). + +* `--num_gradient_servers` + - 梯度服务器的数量,该参数在集群提交环境中自动设置. + - 类型: int32 (默认: 1). + +* `--small_messages` + - 如果消息数据太小,建议将该参数设为true,启动快速应答,无延迟. + - 类型: bool (默认: 0). + +* `--sock_send_buf_size` + - 限制套接字发送缓冲区的大小。如果仔细设置的话,可以有效减小网络的阻塞. + - 类型: int32 (默认: 1024 \* 1024 \* 40). + +* `--sock_recv_buf_size` + - 限制套接字接收缓冲区的大小. + - 类型: int32 (默认: 1024 \* 1024 \* 40). + +* `--parameter_block_size` + - 参数服务器的参数分块大小。如果未设置,将会自动计算出一个合适的值. + - 类型: int32 (默认: 0). + +* `--parameter_block_size_for_sparse` + - 参数服务器稀疏更新的参数分块大小。如果未设置,将会自动计算出一个合适的值. + - 类型: int32 (默认: 0). + +* `--log_period_server` + - 在参数服务器终端每log_period_server个批次打印日志进度. + - 类型: int32 (默认: 500). + +* `--loadsave_parameters_in_pserver` + - 在参数服务器上加载和保存参数,只有当设置了sparse_remote_update参数时才有效. + - 类型: bool (默认: 0). + +* `--pserver_num_threads` + - 同步执行操作的线程数. + - 类型: bool (默认: 1). + +* `--ports_num_for_sparse` + - 发送参数的端口号,根据默认值递增(port + ports_num),用于稀疏训练中. + - 类型: int32 (默认: 0). + +* `--nics` + - 参数服务器的网络设备名称,已经在集群提交环境中完成设置. + - 类型: string (默认: "xgbe0,xgbe1"). + +* `--rdma_tcp` + - 使用rdma还是tcp传输协议,该参数已经在集群提交环境中完成设置. + - 类型: string (默认: "tcp"). + +## 异步随机梯度下降(Async SGD) +* `--async_count` + - 定义异步训练的长度,如果为0,则使用同步训练. + - 类型: int32 (默认: 0). + +* `--async_lagged_ratio_min` + - 控制`config_.async_lagged_grad_discard_ratio()`的最小值. + - 类型: double (默认: 1.0). + +* `--async_lagged_ratio_default` + - 如果在网络配置中未设置async_lagged_grad_discard_ratio,则使用该参数作为默认值. + - 类型: double (默认: 1.5). + +## 性能调优(Performance Tuning) + +* `--log_barrier_abstract` + - 如果为true,则显示阻隔性能的摘要信息. + - 类型: bool (默认: 1). + +* `--log_barrier_show_log` + - 如果为true,则总会显示阻隔摘要信息,即使间隔很小. + - 类型: bool (默认: 0). + +* `--log_barrier_lowest_nodes` + - 最少显示多少个节点. + - 类型: int32 (默认: 5). + +* `--check_sparse_distribution_in_pserver` + - 指示是否检查所有参数服务器上的稀疏参数的分布是均匀的. + - 类型: bool (默认: 0). + +* `--show_check_sparse_distribution_log` + - 指示是否显示参数服务器上的稀疏参数分布的日志细节. + - 类型: bool (默认: 0). + +* `--allow_inefficient_sparse_update` + - 指示是否允许低效率的稀疏更新. + - 类型: bool (默认: 0). + +* `--check_sparse_distribution_batches` + - 每运行多少个批次执行一次稀疏参数分布的检查. + - 类型: int32 (默认: 100). + +* `--check_sparse_distribution_ratio` + - 如果检查到分配在不同参数服务器上的参数的分布不均匀次数大于check_sparse_distribution_ratio * check_sparse_distribution_batches次,程序停止. + - 类型: double (默认: 0.6). + +* `--check_sparse_distribution_unbalance_degree` + - 不同参数服务器上数据大小的最大值与最小值的比率. + - 类型: double (默认: 2). + +## 矩阵/向量/随机数 +* `--enable_parallel_vector` + - 启动并行向量的阈值. + - 类型: int32 (默认: 0). + +* `--seed` + - 随机数的种子。srand(time)的为0. + - 类型: int32 (默认: 1) + +* `--thread_local_rand_use_global_seed` + - 是否将全局种子应用于本地线程的随机数. + - 类型: bool (默认: 0). diff --git a/doc/howto/usage/cmd_parameter/detail_introduction_en.md b/doc/howto/usage/cmd_parameter/detail_introduction_en.md index 27b2faf1d8..4b67266df6 100644 --- a/doc/howto/usage/cmd_parameter/detail_introduction_en.md +++ b/doc/howto/usage/cmd_parameter/detail_introduction_en.md @@ -1,7 +1,3 @@ -```eval_rst -.. _cmd_detail_introduction: -``` - # Detail Description ## Common @@ -73,7 +69,7 @@ - type: bool (default: 0). * `--load_missing_parameter_strategy` - - Specify the loading operation when model file is missing. Now support fail/rand/zere three operations. + - Specify the loading operation when model file is missing. Now support fail/rand/zero three operations. - `fail`: program will exit. - `rand`: uniform or normal distribution according to **initial\_strategy** in network config. Uniform range is: **[mean - std, mean + std]**, where mean and std are configures in trainer config. - `zero`: all parameters are zero. diff --git a/doc/howto/usage/cmd_parameter/index_en.md b/doc/howto/usage/cmd_parameter/index_en.md index 2a96e7e976..bd16affdd8 100644 --- a/doc/howto/usage/cmd_parameter/index_en.md +++ b/doc/howto/usage/cmd_parameter/index_en.md @@ -1,7 +1,4 @@ -```eval_rst -.. _cmd_line_index: -``` -# Set Command-line Parameters +# How to Set Command-line Parameters * [Use Case](use_case_en.md) * [Arguments](arguments_en.md) diff --git a/doc/howto/usage/cmd_parameter/use_case_cn.md b/doc/howto/usage/cmd_parameter/use_case_cn.md new file mode 100644 index 0000000000..db8c39d950 --- /dev/null +++ b/doc/howto/usage/cmd_parameter/use_case_cn.md @@ -0,0 +1,182 @@ +# 使用案例 + +## 本地训练 + +本地训练的实验,诸如图像分类,自然语言处理等,通常都会使用下面这些命令行参数。 + +``` +paddle train \ + --use_gpu=1/0 \ #1:GPU,0:CPU(默认为1) + --config=network_config \ + --save_dir=output \ + --trainer_count=COUNT \ #(默认为1) + --test_period=M \ #(默认为0) + --num_passes=N \ #(默认为100) + --log_period=K \ #(默认为100) + --dot_period=1000 \ #(默认为1) + #[--show_parameter_stats_period=100] \ #(默认为0) + #[--saving_period_by_batches=200] \ #(默认为0) +``` +根据你的任务,可以选择是否使用参数`show_parameter_stats_period`和`saving_period_by_batches`。 + +### 1) 将命令参数传给网络配置 + +`config_args`是一个很有用的参数,用于将参数传递给网络配置。 + +``` +--config_args=generating=1,beam_size=5,layer_num=10 \ +``` +`get_config_arg`可用于在网络配置中解析这些参数,如下所示: + +``` +generating = get_config_arg('generating', bool, False) +beam_size = get_config_arg('beam_size', int, 3) +layer_num = get_config_arg('layer_num', int, 8) +``` + +`get_config_arg`: + +``` +get_config_arg(name, type, default_value) +``` +- name: `--config_args`中指定的名字 +- type: 值类型,包括bool, int, str, float等 +- default_value: 默认值 + +### 2) 使用模型初始化网络 + +增加如下参数: + +``` +--init_model_path=model_path +--load_missing_parameter_strategy=rand +``` + +## 本地测试 + +方法一: + +``` +paddle train --job=test \ + --use_gpu=1/0 \ + --config=network_config \ + --trainer_count=COUNT \ + --init_model_path=model_path \ +``` +- 使用init\_model\_path指定测试的模型 +- 只能测试单个模型 + +方法二: + +``` +paddle train --job=test \ + --use_gpu=1/0 \ + --config=network_config \ + --trainer_count=COUNT \ + --model_list=model.list \ +``` +- 使用model_list指定测试的模型列表 +- 可以测试多个模型,文件model.list如下所示: + +``` +./alexnet_pass1 +./alexnet_pass2 +``` + +方法三: + +``` +paddle train --job=test \ + --use_gpu=1/0 \ + --config=network_config \ + --trainer_count=COUNT \ + --save_dir=model \ + --test_pass=M \ + --num_passes=N \ +``` +这种方式必须使用Paddle存储的模型路径格式,如:`model/pass-%5d`。测试的模型包括从第M轮到第N-1轮存储的所有模型。例如,M=12,N=14这种写法将会测试模型`model/pass-00012`和`model/pass-00013`。 + +## 稀疏训练 + +当输入是维度很高的稀疏数据时,通常使用稀疏训练来加速计算过程。例如,输入数据的字典维数是1百万,但是每个样本仅包含几个词。在Paddle中,稀疏矩阵的乘积应用于前向传播过程,而稀疏更新在反向传播之后的权重更新时进行。 + +### 1) 本地训练 + +用户需要在网络配置中指定**sparse\_update=True**。请参照网络配置的文档了解更详细的信息。 + +### 2) 集群训练 + +在集群上训练一个稀疏模型需要加上下面的参数。同时用户需要在网络配置中指定**sparse\_remote\_update=True**。请参照网络配置的文档了解更详细的信息。 + +``` +--ports_num_for_sparse=1 #(默认为0) +``` + +## parallel_nn +用户可以设置`parallel_nn`来混合使用GPU和CPU计算网络层的参数。也就是说,你可以将网络配置成某些层使用GPU计算,而其他层使用CPU计算。另一种方式是将网络层划分到不同的GPU上去计算,这样可以减小GPU内存,或者采用并行计算来加速某些层的更新。 + +如果你想使用这些特性,你需要在网络配置中指定设备的ID号(表示为deviceId),并且加上下面的命令行参数: + +``` +--parallel_nn=true +``` +### 案例一:GPU和CPU混合使用 +请看下面的例子: + +``` +#command line: +paddle train --use_gpu=true --parallel_nn=true trainer_count=COUNT + +default_device(0) + +fc1=fc_layer(...) +fc2=fc_layer(...) +fc3=fc_layer(...,layer_attr=ExtraAttr(device=-1)) + +``` +- default_device(0): 设置默认设备号为0。这意味着除了指定device=-1的层之外,其他所有层都会使用GPU计算,每层使用的GPU号依赖于参数trainer\_count和gpu\_id(默认为0)。在此,fc1和fc2层在GPU上计算。 + +- device=-1: fc3层使用CPU计算。 + +- trainer_count: + - trainer_count=1: 如果未设置gpu\_id,那么fc1和fc2层将会使用第1个GPU来计算。否则使用gpu\_id指定的GPU。 + + - trainer_count>1: 在trainer\_count个GPU上使用数据并行来计算某一层。例如,trainer\_count=2意味着0号和1号GPU将会使用数据并行来计算fc1和fc2层。 + +### 案例二:在不同设备上指定层 + +``` +#command line: +paddle train --use_gpu=true --parallel_nn=true --trainer_count=COUNT + +#network: +fc2=fc_layer(input=l1, layer_attr=ExtraAttr(device=0), ...) +fc3=fc_layer(input=l1, layer_attr=ExtraAttr(device=1), ...) +fc4=fc_layer(input=fc2, layer_attr=ExtraAttr(device=-1), ...) +``` +在本例中,我们假设一台机器上有4个GPU。 + +- trainer_count=1: + - 使用0号GPU计算fc2层。 + - 使用1号GPU计算fc3层。 + - 使用CPU计算fc4层。 + +- trainer_count=2: + - 使用0号和1号GPU计算fc2层。 + - 使用2号和3号GPU计算fc3层。 + - 使用CPU两线程计算fc4层。 + +- trainer_count=4: + - 运行失败(注意到我们已经假设机器上有4个GPU),因为参数`allow_only_one_model_on_one_gpu`默认设置为真。 + +**当`device!=-1`时设备ID号的分配:** + +``` +(deviceId + gpu_id + threadId * numLogicalDevices_) % numDevices_ + +deviceId: 在层中指定 +gpu_id: 默认为0 +threadId: 线程ID号,范围: 0,1,..., trainer_count-1 +numDevices_: 机器的设备(GPU)数目 +numLogicalDevices_: min(max(deviceId + 1), numDevices_) +``` diff --git a/doc/howto/usage/cmd_parameter/use_case_en.md b/doc/howto/usage/cmd_parameter/use_case_en.md index 4d7bb33f36..e287f0c4b9 100644 --- a/doc/howto/usage/cmd_parameter/use_case_en.md +++ b/doc/howto/usage/cmd_parameter/use_case_en.md @@ -134,14 +134,14 @@ fc2=fc_layer(...) fc3=fc_layer(...,layer_attr=ExtraAttr(device=-1)) ``` -- default_device(0): set default device ID to 0. This means that except the layers with device=-1, all layers will use a GPU, and the specific GPU used for each layer depends on trainer\_count and gpu\_id (0 by default). Here, layer l1 and l2 are computed on the GPU. +- default_device(0): set default device ID to 0. This means that except the layers with device=-1, all layers will use a GPU, and the specific GPU used for each layer depends on trainer\_count and gpu\_id (0 by default). Here, layer fc1 and fc2 are computed on the GPU. -- device=-1: use the CPU for layer l3. +- device=-1: use the CPU for layer fc3. - trainer_count: - - trainer_count=1: if gpu\_id is not set, then use the first GPU to compute layers l1 and l2. Otherwise use the GPU with gpu\_id. + - trainer_count=1: if gpu\_id is not set, then use the first GPU to compute layers fc1 and fc2. Otherwise use the GPU with gpu\_id. - - trainer_count>1: use trainer\_count GPUs to compute one layer using data parallelism. For example, trainer\_count=2 means that GPUs 0 and 1 will use data parallelism to compute layer l1 and l2. + - trainer_count>1: use trainer\_count GPUs to compute one layer using data parallelism. For example, trainer\_count=2 means that GPUs 0 and 1 will use data parallelism to compute layer fc1 and fc2. ### Case 2: Specify Layers in Different Devices @@ -157,14 +157,14 @@ fc4=fc_layer(input=fc2, layer_attr=ExtraAttr(device=-1), ...) In this case, we assume that there are 4 GPUs in one machine. - trainer_count=1: - - Use GPU 0 to compute layer l2. - - Use GPU 1 to compute layer l3. - - Use CPU to compute layer l4. + - Use GPU 0 to compute layer fc2. + - Use GPU 1 to compute layer fc3. + - Use CPU to compute layer fc4. - trainer_count=2: - - Use GPU 0 and 1 to compute layer l2. - - Use GPU 2 and 3 to compute layer l3. - - Use CPU to compute l4 in two threads. + - Use GPU 0 and 1 to compute layer fc2. + - Use GPU 2 and 3 to compute layer fc3. + - Use CPU to compute fc4 in two threads. - trainer_count=4: - It will fail (note, we have assumed that there are 4 GPUs in machine), because argument `allow_only_one_model_on_one_gpu` is true by default. From 843b63bb84586c2b861d971865be270b60a87c56 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 21 Dec 2016 19:26:05 +0800 Subject: [PATCH 034/119] add config_parser in trainer_config_helpers to seperate trainer config --- demo/mnist/api_train.py | 28 ++++++-- python/paddle/trainer/config_parser.py | 70 ++++++++++--------- .../paddle/trainer_config_helpers/__init__.py | 1 + .../trainer_config_helpers/config_parser.py | 38 ++++++++++ 4 files changed, 98 insertions(+), 39 deletions(-) create mode 100644 python/paddle/trainer_config_helpers/config_parser.py diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 48ba61c47d..8fa286b5f9 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -9,11 +9,29 @@ The user api could be simpler and carefully designed. import py_paddle.swig_paddle as api from py_paddle import DataProviderConverter import paddle.trainer.PyDataProvider2 as dp -import paddle.trainer.config_parser import numpy as np import random from mnist_util import read_from_mnist +import paddle.trainer_config_helpers.config_parser as config_parser +from paddle.trainer_config_helpers import * + + +def optimizer_config(): + settings( + learning_rate=1e-4, learning_method=AdamOptimizer(), batch_size=1000) + + +def network_config(): + imgs = data_layer(name='pixel', size=784) + hidden1 = fc_layer(input=imgs, size=200) + hidden2 = fc_layer(input=hidden1, size=200) + inference = fc_layer(input=hidden2, size=10, act=SoftmaxActivation()) + cost = classification_cost( + input=inference, label=data_layer( + name='label', size=10)) + outputs(cost) + def init_parameter(network): assert isinstance(network, api.GradientMachine) @@ -54,20 +72,20 @@ def input_order_converter(generator): def main(): api.initPaddle("-use_gpu=false", "-trainer_count=4") # use 4 cpu cores - config = paddle.trainer.config_parser.parse_config( - 'simple_mnist_network.py', '') # get enable_types for each optimizer. # enable_types = [value, gradient, momentum, etc] # For each optimizer(SGD, Adam), GradientMachine should enable different # buffers. - opt_config = api.OptimizationConfig.createFromProto(config.opt_config) + opt_config_proto = config_parser.parse_optimizer_config(optimizer_config) + opt_config = api.OptimizationConfig.createFromProto(opt_config_proto) _temp_optimizer_ = api.ParameterOptimizer.create(opt_config) enable_types = _temp_optimizer_.getParameterTypes() # Create Simple Gradient Machine. + model_config = config_parser.parse_network_config(network_config) m = api.GradientMachine.createFromConfigProto( - config.model_config, api.CREATE_MODE_NORMAL, enable_types) + model_config, api.CREATE_MODE_NORMAL, enable_types) # This type check is not useful. Only enable type hint in IDE. # Such as PyCharm diff --git a/python/paddle/trainer/config_parser.py b/python/paddle/trainer/config_parser.py index 2eb7b17a0b..674b5ac58b 100644 --- a/python/paddle/trainer/config_parser.py +++ b/python/paddle/trainer/config_parser.py @@ -3416,8 +3416,35 @@ def register_parse_config_hook(f): _parse_config_hooks.add(f) -def parse_config(config_file, config_arg_str): +def update_g_config(): ''' + Update g_config after execute config_file or config_functions. + ''' + for k, v in settings.iteritems(): + if v is None: + continue + g_config.opt_config.__setattr__(k, v) + + for k, v in trainer_settings.iteritems(): + if v is None: + continue + g_config.__setattr__(k, v) + + for name in g_config.model_config.input_layer_names: + assert name in g_layer_map, \ + 'input name "%s" does not correspond to a layer name' % name + assert (g_layer_map[name].type == "data" or g_layer_map[name].type == "data_trim"), \ + 'The type of input layer "%s" is not "data"' % name + for name in g_config.model_config.output_layer_names: + assert name in g_layer_map, \ + 'input name "%s" does not correspond to a layer name' % name + return g_config + + +def parse_config(trainer_config, config_arg_str): + ''' + @param trainer_config: can be a string of config file name or a function name + with config logic @param config_arg_str: a string of the form var1=val1,var2=val2. It will be passed to config script as a dictionary CONFIG_ARGS ''' @@ -3451,45 +3478,20 @@ def parse_config(config_file, config_arg_str): g_root_submodel.is_recurrent_layer_group = False g_current_submodel = g_root_submodel - # for paddle on spark, need support non-file config. - # you can use parse_config like below: - # - # from paddle.trainer.config_parser import parse_config - # def configs(): - # #your paddle config code, which is same as config file. - # - # config = parse_config(configs, "is_predict=1") - # # then you get config proto object. - if hasattr(config_file, '__call__'): - config_file.func_globals.update( + if hasattr(trainer_config, '__call__'): + trainer_config.func_globals.update( make_config_environment("", config_args)) - config_file() + trainer_config() else: - execfile(config_file, make_config_environment(config_file, config_args)) - for k, v in settings.iteritems(): - if v is None: - continue - g_config.opt_config.__setattr__(k, v) - - for k, v in trainer_settings.iteritems(): - if v is None: - continue - g_config.__setattr__(k, v) + execfile(trainer_config, + make_config_environment(trainer_config, config_args)) - for name in g_config.model_config.input_layer_names: - assert name in g_layer_map, \ - 'input name "%s" does not correspond to a layer name' % name - assert (g_layer_map[name].type == "data" or g_layer_map[name].type == "data_trim"), \ - 'The type of input layer "%s" is not "data"' % name - for name in g_config.model_config.output_layer_names: - assert name in g_layer_map, \ - 'input name "%s" does not correspond to a layer name' % name - return g_config + return update_g_config() -def parse_config_and_serialize(config_file, config_arg_str): +def parse_config_and_serialize(trainer_config, config_arg_str): try: - config = parse_config(config_file, config_arg_str) + config = parse_config(trainer_config, config_arg_str) #logger.info(config) return config.SerializeToString() except: diff --git a/python/paddle/trainer_config_helpers/__init__.py b/python/paddle/trainer_config_helpers/__init__.py index a2335768b9..84ed40a036 100644 --- a/python/paddle/trainer_config_helpers/__init__.py +++ b/python/paddle/trainer_config_helpers/__init__.py @@ -20,6 +20,7 @@ from layers import * from networks import * from optimizers import * from attrs import * +from config_parser import * # This will enable operator overload for LayerOutput import math as layer_math diff --git a/python/paddle/trainer_config_helpers/config_parser.py b/python/paddle/trainer_config_helpers/config_parser.py new file mode 100644 index 0000000000..4b91b8d282 --- /dev/null +++ b/python/paddle/trainer_config_helpers/config_parser.py @@ -0,0 +1,38 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle.trainer.config_parser as config_parser +''' +This file is a wrapper of formal config_parser. The main idea of this file is to +separete different config logic into different function, such as network configuration + and optimizer configuration. +''' + +__all__ = [ + "parse_trainer_config", "parse_network_config", "parse_optimizer_config" +] + + +def parse_trainer_config(trainer_conf, config_arg_str): + return config_parser.parse_config(trainer_conf, config_arg_str) + + +def parse_network_config(network_conf): + config = config_parser.parse_config(network_conf, '') + return config.model_config + + +def parse_optimizer_config(optimizer_conf): + config = config_parser.parse_config(optimizer_conf, '') + return config.opt_config From 763a30fdde211c047c2ba77d2d82cfa4152f0f26 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Thu, 22 Dec 2016 18:22:47 +0800 Subject: [PATCH 035/119] add config_parser_utils --- demo/mnist/api_train.py | 14 ++++--- demo/mnist/simple_mnist_network.py | 21 ---------- .../paddle/trainer_config_helpers/__init__.py | 2 +- .../config_parser_utils.py | 38 +++++++++++++++++++ 4 files changed, 48 insertions(+), 27 deletions(-) delete mode 100644 demo/mnist/simple_mnist_network.py create mode 100644 python/paddle/trainer_config_helpers/config_parser_utils.py diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 8fa286b5f9..924bd39a50 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -12,14 +12,17 @@ import paddle.trainer.PyDataProvider2 as dp import numpy as np import random from mnist_util import read_from_mnist - -import paddle.trainer_config_helpers.config_parser as config_parser +import paddle.trainer_config_helpers.config_parser_utils as config_parser_utils from paddle.trainer_config_helpers import * def optimizer_config(): settings( - learning_rate=1e-4, learning_method=AdamOptimizer(), batch_size=1000) + learning_rate=1e-4, + learning_method=AdamOptimizer(), + batch_size=1000, + model_average=ModelAverage(average_window=0.5), + regularization=L2Regularization(rate=0.5)) def network_config(): @@ -77,13 +80,14 @@ def main(): # enable_types = [value, gradient, momentum, etc] # For each optimizer(SGD, Adam), GradientMachine should enable different # buffers. - opt_config_proto = config_parser.parse_optimizer_config(optimizer_config) + opt_config_proto = config_parser_utils.parse_optimizer_config( + optimizer_config) opt_config = api.OptimizationConfig.createFromProto(opt_config_proto) _temp_optimizer_ = api.ParameterOptimizer.create(opt_config) enable_types = _temp_optimizer_.getParameterTypes() # Create Simple Gradient Machine. - model_config = config_parser.parse_network_config(network_config) + model_config = config_parser_utils.parse_network_config(network_config) m = api.GradientMachine.createFromConfigProto( model_config, api.CREATE_MODE_NORMAL, enable_types) diff --git a/demo/mnist/simple_mnist_network.py b/demo/mnist/simple_mnist_network.py deleted file mode 100644 index f5d1ea169e..0000000000 --- a/demo/mnist/simple_mnist_network.py +++ /dev/null @@ -1,21 +0,0 @@ -from paddle.trainer_config_helpers import * - -settings( - learning_rate=1e-4, - learning_method=AdamOptimizer(), - batch_size=1000, - model_average=ModelAverage(average_window=0.5), - regularization=L2Regularization(rate=0.5)) - -imgs = data_layer(name='pixel', size=784) - -hidden1 = fc_layer(input=imgs, size=200) -hidden2 = fc_layer(input=hidden1, size=200) - -inference = fc_layer(input=hidden2, size=10, act=SoftmaxActivation()) - -cost = classification_cost( - input=inference, label=data_layer( - name='label', size=10)) - -outputs(cost) diff --git a/python/paddle/trainer_config_helpers/__init__.py b/python/paddle/trainer_config_helpers/__init__.py index 84ed40a036..ef9859f831 100644 --- a/python/paddle/trainer_config_helpers/__init__.py +++ b/python/paddle/trainer_config_helpers/__init__.py @@ -20,7 +20,7 @@ from layers import * from networks import * from optimizers import * from attrs import * -from config_parser import * +from config_parser_utils import * # This will enable operator overload for LayerOutput import math as layer_math diff --git a/python/paddle/trainer_config_helpers/config_parser_utils.py b/python/paddle/trainer_config_helpers/config_parser_utils.py new file mode 100644 index 0000000000..681b177a55 --- /dev/null +++ b/python/paddle/trainer_config_helpers/config_parser_utils.py @@ -0,0 +1,38 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle.trainer.config_parser as config_parser +''' +This file is a wrapper of formal config_parser. The main idea of this file is to +separete different config logic into different function, such as network configuration + and optimizer configuration. +''' + +__all__ = [ + "parse_trainer_config", "parse_network_config", "parse_optimizer_config" +] + + +def parse_trainer_config(trainer_conf, config_arg_str): + return config_parser.parse_config(trainer_conf, config_arg_str) + + +def parse_network_config(network_conf, config_arg_str=''): + config = config_parser.parse_config(network_conf, config_arg_str) + return config.model_config + + +def parse_optimizer_config(optimizer_conf, config_arg_str=''): + config = config_parser.parse_config(optimizer_conf, config_arg_str) + return config.opt_config From 9b41b08ef39aaf4f49daaf85a8defd4726642e69 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Thu, 22 Dec 2016 21:24:14 +0800 Subject: [PATCH 036/119] Remove unnecessary import in api_train.py --- demo/mnist/api_train.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/demo/mnist/api_train.py b/demo/mnist/api_train.py index 924bd39a50..f301da382f 100644 --- a/demo/mnist/api_train.py +++ b/demo/mnist/api_train.py @@ -12,7 +12,6 @@ import paddle.trainer.PyDataProvider2 as dp import numpy as np import random from mnist_util import read_from_mnist -import paddle.trainer_config_helpers.config_parser_utils as config_parser_utils from paddle.trainer_config_helpers import * @@ -80,14 +79,13 @@ def main(): # enable_types = [value, gradient, momentum, etc] # For each optimizer(SGD, Adam), GradientMachine should enable different # buffers. - opt_config_proto = config_parser_utils.parse_optimizer_config( - optimizer_config) + opt_config_proto = parse_optimizer_config(optimizer_config) opt_config = api.OptimizationConfig.createFromProto(opt_config_proto) _temp_optimizer_ = api.ParameterOptimizer.create(opt_config) enable_types = _temp_optimizer_.getParameterTypes() # Create Simple Gradient Machine. - model_config = config_parser_utils.parse_network_config(network_config) + model_config = parse_network_config(network_config) m = api.GradientMachine.createFromConfigProto( model_config, api.CREATE_MODE_NORMAL, enable_types) From 8a47bbd64148cf2dfb1bc0855ea767011b70cfdd Mon Sep 17 00:00:00 2001 From: lifu Date: Fri, 23 Dec 2016 10:33:19 +0800 Subject: [PATCH 037/119] modify documents --- doc/howto/usage/cmd_parameter/detail_introduction_en.md | 4 ++++ doc/howto/usage/cmd_parameter/index_en.md | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/howto/usage/cmd_parameter/detail_introduction_en.md b/doc/howto/usage/cmd_parameter/detail_introduction_en.md index 4b67266df6..c4136adeba 100644 --- a/doc/howto/usage/cmd_parameter/detail_introduction_en.md +++ b/doc/howto/usage/cmd_parameter/detail_introduction_en.md @@ -1,3 +1,7 @@ +```eval_rst +.. _cmd_detail_introduction: +``` + # Detail Description ## Common diff --git a/doc/howto/usage/cmd_parameter/index_en.md b/doc/howto/usage/cmd_parameter/index_en.md index bd16affdd8..c27b572dbe 100644 --- a/doc/howto/usage/cmd_parameter/index_en.md +++ b/doc/howto/usage/cmd_parameter/index_en.md @@ -1,5 +1,9 @@ -# How to Set Command-line Parameters +```eval_rst +.. _cmd_line_index: +``` +# Set Command-line Parameters * [Use Case](use_case_en.md) * [Arguments](arguments_en.md) * [Detailed Descriptions](detail_introduction_en.md) + From e9954cf395749b090be1fa9ca824ca2bf77068ee Mon Sep 17 00:00:00 2001 From: lifu Date: Fri, 23 Dec 2016 11:01:24 +0800 Subject: [PATCH 038/119] modify documents --- doc/howto/usage/cmd_parameter/index_en.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/howto/usage/cmd_parameter/index_en.md b/doc/howto/usage/cmd_parameter/index_en.md index c27b572dbe..2a96e7e976 100644 --- a/doc/howto/usage/cmd_parameter/index_en.md +++ b/doc/howto/usage/cmd_parameter/index_en.md @@ -6,4 +6,3 @@ * [Use Case](use_case_en.md) * [Arguments](arguments_en.md) * [Detailed Descriptions](detail_introduction_en.md) - From cf498cfad509ff0964329122fad5998bda641ed2 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 23 Dec 2016 12:00:02 +0800 Subject: [PATCH 039/119] move TestMain.cpp into utils and fix some tests for check --- paddle/function/CMakeLists.txt | 2 -- paddle/math/tests/test_Allocator.cpp | 6 ------ paddle/math/tests/test_BaseMatrix.cpp | 6 ------ paddle/math/tests/test_Matrix.cpp | 6 ------ paddle/utils/CMakeLists.txt | 2 ++ paddle/{function => utils}/TestMain.cpp | 0 6 files changed, 2 insertions(+), 20 deletions(-) rename paddle/{function => utils}/TestMain.cpp (100%) diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 0697842bbe..c997448749 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -11,8 +11,6 @@ endif() add_library(paddle_function STATIC ${cpp_files} ${cu_objs}) -add_library(paddle_test_main STATIC TestMain.cpp) - if(WITH_GPU) # TODO: # file(GLOB test_files . *_op_test.cpp) diff --git a/paddle/math/tests/test_Allocator.cpp b/paddle/math/tests/test_Allocator.cpp index 33e0952efe..1ca70ea84c 100644 --- a/paddle/math/tests/test_Allocator.cpp +++ b/paddle/math/tests/test_Allocator.cpp @@ -120,9 +120,3 @@ TEST(MemoryHandle, Gpu) { } } #endif - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - initMain(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/math/tests/test_BaseMatrix.cpp b/paddle/math/tests/test_BaseMatrix.cpp index cc7c1e7eb2..21918b86e1 100644 --- a/paddle/math/tests/test_BaseMatrix.cpp +++ b/paddle/math/tests/test_BaseMatrix.cpp @@ -242,10 +242,4 @@ TEST(BaseMatrix, Other) { } } -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - paddle::initMain(argc, argv); - return RUN_ALL_TESTS(); -} - #endif diff --git a/paddle/math/tests/test_Matrix.cpp b/paddle/math/tests/test_Matrix.cpp index adb5fbd9fa..6899769144 100644 --- a/paddle/math/tests/test_Matrix.cpp +++ b/paddle/math/tests/test_Matrix.cpp @@ -291,10 +291,4 @@ TEST(Matrix, multiBinaryCrossEntropy) { } } -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - paddle::initMain(argc, argv); - return RUN_ALL_TESTS(); -} - #endif diff --git a/paddle/utils/CMakeLists.txt b/paddle/utils/CMakeLists.txt index 45240b5002..cf5a833014 100644 --- a/paddle/utils/CMakeLists.txt +++ b/paddle/utils/CMakeLists.txt @@ -5,6 +5,8 @@ file(GLOB UTIL_SOURCES . *.cpp) create_resources(enable_virtualenv.py enable_virtualenv.c) set(UTIL_RES enable_virtualenv.c) +add_library(paddle_test_main STATIC TestMain.cpp) + if(APPLE) file(GLOB UTIL_ARCH_SOURCES . arch/osx/*.cpp) else() diff --git a/paddle/function/TestMain.cpp b/paddle/utils/TestMain.cpp similarity index 100% rename from paddle/function/TestMain.cpp rename to paddle/utils/TestMain.cpp From 4af4883f22f9ae1ab652bfcc72f907fa555ea066 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 23 Dec 2016 13:03:42 +0800 Subject: [PATCH 040/119] fix some tests which only need paddle::initMain in main --- paddle/gserver/tests/test_LinearChainCRF.cpp | 6 ------ paddle/gserver/tests/test_ProtoDataProvider.cpp | 6 ------ paddle/gserver/tests/test_WarpCTCLayer.cpp | 6 ------ paddle/math/tests/test_CpuGpuVector.cpp | 7 ------- paddle/math/tests/test_ExecViaCpu.cpp | 6 ------ paddle/math/tests/test_SIMDFunctions.cpp | 6 ------ paddle/math/tests/test_SparseMatrix.cpp | 6 ------ paddle/math/tests/test_Tensor.cu | 8 -------- paddle/math/tests/test_TrainingAlgorithm.cpp | 8 -------- paddle/math/tests/test_batchTranspose.cpp | 6 ------ paddle/math/tests/test_lazyAssign.cu | 8 -------- paddle/math/tests/test_matrixCompare.cpp | 6 ------ paddle/math/tests/test_sparseMatrixCompare.cpp | 7 ------- paddle/parameter/tests/test_common.cpp | 9 --------- paddle/utils/tests/test_CustomStackTrace.cpp | 6 ------ paddle/utils/tests/test_SIMDFlags.cpp | 5 ----- paddle/utils/tests/test_SpinLock.cpp | 6 ------ paddle/utils/tests/test_Thread.cpp | 5 ----- paddle/utils/tests/test_ThreadBarrier.cpp | 6 ------ 19 files changed, 123 deletions(-) diff --git a/paddle/gserver/tests/test_LinearChainCRF.cpp b/paddle/gserver/tests/test_LinearChainCRF.cpp index 330adee8f7..f046cb0b28 100644 --- a/paddle/gserver/tests/test_LinearChainCRF.cpp +++ b/paddle/gserver/tests/test_LinearChainCRF.cpp @@ -65,9 +65,3 @@ TEST(LinearChainCRF, decoding) { } } } - -int main(int argc, char** argv) { - initMain(argc, argv); - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/gserver/tests/test_ProtoDataProvider.cpp b/paddle/gserver/tests/test_ProtoDataProvider.cpp index d421b6e2f2..8fc0aaab69 100644 --- a/paddle/gserver/tests/test_ProtoDataProvider.cpp +++ b/paddle/gserver/tests/test_ProtoDataProvider.cpp @@ -730,9 +730,3 @@ TEST(ProtoSequenceDataProvider, test) { } // end for (int numIdSlots : numSlotsArray) } // end for (int numSparseNonValueVecSlots : numSlotsArray) } - -int main(int argc, char** argv) { - initMain(argc, argv); - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/gserver/tests/test_WarpCTCLayer.cpp b/paddle/gserver/tests/test_WarpCTCLayer.cpp index 0a4a814d52..dab6366588 100644 --- a/paddle/gserver/tests/test_WarpCTCLayer.cpp +++ b/paddle/gserver/tests/test_WarpCTCLayer.cpp @@ -242,9 +242,3 @@ TEST(Layer, WarpCTCLayer) { } } } - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - initMain(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/math/tests/test_CpuGpuVector.cpp b/paddle/math/tests/test_CpuGpuVector.cpp index 624fa20ca5..58bc43a38b 100644 --- a/paddle/math/tests/test_CpuGpuVector.cpp +++ b/paddle/math/tests/test_CpuGpuVector.cpp @@ -77,11 +77,4 @@ TEST(CpuGpuVector, subCreate) { checkDataEqual(v1Check->getData() + offset, v2Check->getData(), size2); } -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - initMain(argc, argv); - int ret = RUN_ALL_TESTS(); - return ret; -} - #endif diff --git a/paddle/math/tests/test_ExecViaCpu.cpp b/paddle/math/tests/test_ExecViaCpu.cpp index 27216ddb58..04c856453d 100644 --- a/paddle/math/tests/test_ExecViaCpu.cpp +++ b/paddle/math/tests/test_ExecViaCpu.cpp @@ -114,9 +114,3 @@ TEST(ExecViaCpu, test1) { testWrapper(functor); } #endif - -int main(int argc, char** argv) { - paddle::initMain(argc, argv); - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/math/tests/test_SIMDFunctions.cpp b/paddle/math/tests/test_SIMDFunctions.cpp index f62843310d..e8f9b26ff2 100644 --- a/paddle/math/tests/test_SIMDFunctions.cpp +++ b/paddle/math/tests/test_SIMDFunctions.cpp @@ -169,9 +169,3 @@ TEST(SIMDFunction, decayL1_WithoutLR) { ASSERT_NEAR(dest[i], simd_dest[i], EPSILON); } } - -int main(int argc, char** argv) { - paddle::initMain(argc, argv); - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/math/tests/test_SparseMatrix.cpp b/paddle/math/tests/test_SparseMatrix.cpp index 0949ab7ffb..9d3fbaef43 100644 --- a/paddle/math/tests/test_SparseMatrix.cpp +++ b/paddle/math/tests/test_SparseMatrix.cpp @@ -561,9 +561,3 @@ TEST(Matrix, SparseMatrixCSCFormatTrimFrom) { checkSMatrixEqual2(matA, matD); #endif } - -int main(int argc, char** argv) { - paddle::initMain(argc, argv); - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/math/tests/test_Tensor.cu b/paddle/math/tests/test_Tensor.cu index 1859b9fc13..40e38434fa 100644 --- a/paddle/math/tests/test_Tensor.cu +++ b/paddle/math/tests/test_Tensor.cu @@ -1163,11 +1163,3 @@ TEST(Quaternary, CompareOp) { TestQuaternaryMatrix testGpu(testQuaternaryCompareOp); #endif } - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - hl_start(); - hl_init(0); - return RUN_ALL_TESTS(); -} - diff --git a/paddle/math/tests/test_TrainingAlgorithm.cpp b/paddle/math/tests/test_TrainingAlgorithm.cpp index 2c458cba9c..4a88844b43 100644 --- a/paddle/math/tests/test_TrainingAlgorithm.cpp +++ b/paddle/math/tests/test_TrainingAlgorithm.cpp @@ -459,11 +459,3 @@ void testSparseMomentum(size_t size, bool useGpu) { } TEST(Training, SparseMomentum) { testCase(testSparseMomentum); } - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - initMain(argc, argv); - hl_start(); - hl_init(FLAGS_gpu_id); - return RUN_ALL_TESTS(); -} diff --git a/paddle/math/tests/test_batchTranspose.cpp b/paddle/math/tests/test_batchTranspose.cpp index 9925e24dc1..4eb9837909 100644 --- a/paddle/math/tests/test_batchTranspose.cpp +++ b/paddle/math/tests/test_batchTranspose.cpp @@ -53,9 +53,3 @@ TEST(MatrixBatchTransTest, test_batch_matrix_transpose) { checkMatrixEqual(cBatchTransMat, cMat_d2h); } #endif - -int main(int argc, char** argv) { - paddle::initMain(argc, argv); - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/math/tests/test_lazyAssign.cu b/paddle/math/tests/test_lazyAssign.cu index 16541edb54..786d863a53 100644 --- a/paddle/math/tests/test_lazyAssign.cu +++ b/paddle/math/tests/test_lazyAssign.cu @@ -139,11 +139,3 @@ TEST(sgdUpdate, GPU) { testMatrixCase(testSgdUpdate); } #endif - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - hl_start(); - hl_init(0); - return RUN_ALL_TESTS(); -} - diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index c6fc849ba0..efda4ff27b 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -1262,10 +1262,4 @@ TEST(Matrix, MaxOutFwdBwd) { } } -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - initMain(argc, argv); - return RUN_ALL_TESTS(); -} - #endif diff --git a/paddle/math/tests/test_sparseMatrixCompare.cpp b/paddle/math/tests/test_sparseMatrixCompare.cpp index dcdbccffc3..a9185a4b24 100644 --- a/paddle/math/tests/test_sparseMatrixCompare.cpp +++ b/paddle/math/tests/test_sparseMatrixCompare.cpp @@ -171,11 +171,4 @@ TEST(SMatrix, sMatrixCollectBias) { } } -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - initMain(argc, argv); - int ret = RUN_ALL_TESTS(); - return ret; -} - #endif diff --git a/paddle/parameter/tests/test_common.cpp b/paddle/parameter/tests/test_common.cpp index aa57a63469..8bab5a6289 100644 --- a/paddle/parameter/tests/test_common.cpp +++ b/paddle/parameter/tests/test_common.cpp @@ -23,15 +23,6 @@ limitations under the License. */ using namespace paddle; // NOLINT -int main(int argc, char** argv) { - paddle::initMain(argc, argv); - testing::InitGoogleTest(&argc, argv); - - int ret = RUN_ALL_TESTS(); - - return ret; -} - class CommonTest : public ::testing::Test { protected: CommonTest() : testStat_("test") {} diff --git a/paddle/utils/tests/test_CustomStackTrace.cpp b/paddle/utils/tests/test_CustomStackTrace.cpp index 18dd0aac43..378788bcec 100644 --- a/paddle/utils/tests/test_CustomStackTrace.cpp +++ b/paddle/utils/tests/test_CustomStackTrace.cpp @@ -96,9 +96,3 @@ TEST(CustomStackTrace, normalTest) { } }); } - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - paddle::initMain(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/utils/tests/test_SIMDFlags.cpp b/paddle/utils/tests/test_SIMDFlags.cpp index 42edede209..8200a24ce7 100644 --- a/paddle/utils/tests/test_SIMDFlags.cpp +++ b/paddle/utils/tests/test_SIMDFlags.cpp @@ -44,8 +44,3 @@ TEST(SIMDFlags, normalPrint) { LOG(INFO) << "Has AVX2: " << std::boolalpha << HAS_AVX2; LOG(INFO) << "Has AVX512: " << std::boolalpha << HAS_AVX512; } - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/utils/tests/test_SpinLock.cpp b/paddle/utils/tests/test_SpinLock.cpp index 605bedb6c9..cc34eb1f86 100644 --- a/paddle/utils/tests/test_SpinLock.cpp +++ b/paddle/utils/tests/test_SpinLock.cpp @@ -53,9 +53,3 @@ TEST(ThreadSpinLock, normalTest) { }); } } - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - paddle::initMain(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/utils/tests/test_Thread.cpp b/paddle/utils/tests/test_Thread.cpp index 2f5c5bbce0..6e2580c491 100644 --- a/paddle/utils/tests/test_Thread.cpp +++ b/paddle/utils/tests/test_Thread.cpp @@ -79,8 +79,3 @@ TEST(AsyncThreadPool, addBatchJobWithResults) { ASSERT_EQ(res[i], i); } } - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/paddle/utils/tests/test_ThreadBarrier.cpp b/paddle/utils/tests/test_ThreadBarrier.cpp index 1237f1b731..554b1c1d4a 100644 --- a/paddle/utils/tests/test_ThreadBarrier.cpp +++ b/paddle/utils/tests/test_ThreadBarrier.cpp @@ -64,9 +64,3 @@ TEST(ThreadBarrier, normalTest) { }); } } - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - paddle::initMain(argc, argv); - return RUN_ALL_TESTS(); -} From 99e43d1d07aa6dc63dba09141529764c7db83198 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 23 Dec 2016 13:20:42 +0800 Subject: [PATCH 041/119] Add c++11 build python binding package --- paddle/api/paddle_ld_flags.py | 7 +++++-- paddle/setup.py.in | 11 ++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/paddle/api/paddle_ld_flags.py b/paddle/api/paddle_ld_flags.py index 7c8206e3fe..b4d27b1cc7 100644 --- a/paddle/api/paddle_ld_flags.py +++ b/paddle/api/paddle_ld_flags.py @@ -141,9 +141,12 @@ try: def c_flag(self): if self.with_coverage: - return ["-fprofile-arcs", "-ftest-coverage", "-O0", "-g"] + return [ + "-fprofile-arcs", "-ftest-coverage", "-O0", "-g", + "-std=c++11" + ] else: - return None + return ["-std=c++11"] except ImportError: class PaddleLDFlag(object): diff --git a/paddle/setup.py.in b/paddle/setup.py.in index b4c38a41b8..464ad63286 100644 --- a/paddle/setup.py.in +++ b/paddle/setup.py.in @@ -30,8 +30,10 @@ is_lin = (system == 'linux') # The extra links will passed from COMAKE # because generate paddle LDFLAGS is too complicated to do in setup.py # it just read COMAKE generated LDFLAGS. +extra_comps = [] extra_links = [] obj = api.paddle_ld_flags.PaddleLDFlag() +extra_comps = obj.c_flag() ldflags = obj.ldflag_str() if ldflags is not None: extra_links.extend(ldflags.split(" ")) @@ -51,20 +53,15 @@ elif is_osx == True: include_dirs = [np.get_include(), "../"] # include numpy and paddle. -extra_c = obj.c_flag() - -attr=dict() -if extra_c is not None: - attr["extra_compile_args"] = extra_c - setup(name="py_paddle", version="@PADDLE_VERSION@", ext_modules=[ Extension('py_paddle._swig_paddle', # Build SWIG Extension. ['Paddle_wrap.cxx'], + language = "c++", include_dirs = include_dirs, extra_link_args = extra_links, - **attr + extra_compile_args = extra_comps ) ], packages=['py_paddle'], From c8d0791accb7fbceda308756e6271e12e233c063 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 23 Dec 2016 13:21:48 +0800 Subject: [PATCH 042/119] Add common.h and remove DisableCopy and Typedefs --- .../image_classification/index_cn.md | 205 ++++++++++++++++++ .../image_classification/index_en.md | 2 +- paddle/api/PaddleAPI.h | 34 ++- paddle/cuda/include/hl_base.h | 66 +++--- paddle/gserver/dataproviders/DataProvider.h | 2 +- .../gserver/layers/BatchNormalizationLayer.h | 2 + paddle/gserver/layers/GruCompute.h | 2 +- paddle/gserver/layers/LstmCompute.h | 2 +- paddle/gserver/layers/MultinomialSampler.h | 2 +- paddle/math/BaseMatrix.h | 2 +- paddle/math/Matrix.h | 2 +- paddle/math/TensorExpression.h | 2 +- paddle/math/Vector.h | 2 +- paddle/parameter/ParallelParameter.h | 2 +- paddle/parameter/Parameter.h | 2 +- paddle/parameter/ParameterUpdateFunctions.h | 2 +- paddle/pserver/BaseClient.h | 2 +- paddle/pserver/ParameterClient2.h | 2 +- paddle/pserver/ParameterServer2.h | 2 +- paddle/utils/CpuId.h | 2 +- paddle/utils/DisableCopy.h | 23 -- paddle/utils/Locks.h | 2 +- paddle/utils/Util.h | 3 +- paddle/utils/Version.h | 2 +- paddle/utils/{TypeDefs.h => common.h} | 15 +- 25 files changed, 277 insertions(+), 107 deletions(-) create mode 100644 doc/tutorials/image_classification/index_cn.md delete mode 100644 paddle/utils/DisableCopy.h rename paddle/utils/{TypeDefs.h => common.h} (71%) diff --git a/doc/tutorials/image_classification/index_cn.md b/doc/tutorials/image_classification/index_cn.md new file mode 100644 index 0000000000..87f465522a --- /dev/null +++ b/doc/tutorials/image_classification/index_cn.md @@ -0,0 +1,205 @@ +图像分类教程 +========== + +在本教程中,我们将使用CIFAR-10数据集训练一个卷积神经网络,并使用这个神经网络来对图片进行分类。如下图所示,卷积神经网络可以辨识图片中的主体,并给出分类结果。 +
![Image Classification](./image_classification.png)
+ +## 数据准备 +首先下载CIFAR-10数据集。下面是CIFAR-10数据集的官方网址: + + + +我们准备了一个脚本,可以用于从官方网站上下载CIFAR-10数据集,转为jpeg文件并存入特定的目录。使用这个脚本前请确认已经安装了pillow及相关依赖模块。可以参照下面的命令进行安装: + +1. 安装pillow + +```bash +sudo apt-get install libjpeg-dev +pip install pillow +``` + +2. 下载数据集 + +```bash +cd demo/image_classification/data/ +sh download_cifar.sh +``` + +CIFAR-10数据集包含60000张32x32的彩色图片。图片分为10类,每个类包含6000张。其中50000张图片作为训练集,10000张作为测试集。 + +下图展示了所有的图片类别,每个类别中随机抽取了10张图片。 +
![Image Classification](./cifar.png)
+ +脚本运行完成后,我们应当会得到一个名为cifar-out的文件夹,其下子文件夹的结构如下 + + +``` +train +---airplane +---automobile +---bird +---cat +---deer +---dog +---frog +---horse +---ship +---truck +test +---airplane +---automobile +---bird +---cat +---deer +---dog +---frog +---horse +---ship +---truck +``` + +cifar-out下包含`train`和`test`两个文件夹,其中分别包含了CIFAR-10中的训练集和测试集。这两个文件夹下各自有10个子文件夹,每个子文件夹下存储相应分类的图片。将图片按照上述结构存储好之后,我们就可以着手对分类模型进行训练了。 + +## 预处理 +数据下载之后,还需要进行预处理,将数据转换为Paddle的格式。我们可以通过如下命令进行预处理工作: + +``` +cd demo/image_classification/ +sh preprocess.sh +``` + +其中`preprocess.sh` 调用 `./demo/image_classification/preprocess.py` 对图片进行预处理 +```sh +export PYTHONPATH=$PYTHONPATH:../../ +data_dir=./data/cifar-out +python preprocess.py -i $data_dir -s 32 -c 1 +``` + +`./demo/image_classification/preprocess.py` 使用如下参数: + +- `-i` 或 `--input` 给出输入数据所在路径; +- `-s` 或 `--size` 给出图片尺寸; +- `-c` 或 `--color` 标示图片是彩色图或灰度图 + +## 模型训练 +在开始训练之前,我们需要先创建一个模型配置文件。下面我们给出了一个配置示例。**注意**,这里的列出的和`vgg_16_cifar.py`文件稍有差别,因为该文件可适用于预测。 + +```python +from paddle.trainer_config_helpers import * +data_dir='data/cifar-out/batches/' +meta_path=data_dir+'batches.meta' +args = {'meta':meta_path, 'mean_img_size': 32, + 'img_size': 32, 'num_classes': 10, + 'use_jpeg': 1, 'color': "color"} +define_py_data_sources2(train_list=data_dir+"train.list", + test_list=data_dir+'test.list', + module='image_provider', + obj='processData', + args=args) +settings( + batch_size = 128, + learning_rate = 0.1 / 128.0, + learning_method = MomentumOptimizer(0.9), + regularization = L2Regularization(0.0005 * 128)) + +img = data_layer(name='image', size=3*32*32) +lbl = data_layer(name="label", size=10) +# small_vgg is predined in trainer_config_helpers.network +predict = small_vgg(input_image=img, num_channels=3) +outputs(classification_cost(input=predict, label=lbl)) +``` + +在第一行中我们载入用于定义网络的函数。 +```python +from paddle.trainer_config_helpers import * +``` + +之后定义的`define_py_data_sources2`使用Python数据提供器,其中 `args`将在`image_provider.py`进行使用,该文件负责产生图片数据并传递给Paddle系统 + - `meta`: 训练集平均值。 + - `mean_img_size`: 平均特征图的高度及宽度。 + - `img_size`:输入图片的高度及宽度。 + - `num_classes`:类别个数。 + - `use_jpeg`:处理过程中数据存储格式。 + - `color`:标示是否为彩色图片。 + + `settings`用于设置训练算法。在下面的例子中,learning rate被设置为0.1除以batch size,而weight decay则为0.0005乘以batch size。 + + ```python +settings( + batch_size = 128, + learning_rate = 0.1 / 128.0, + learning_method = MomentumOptimizer(0.9), + regularization = L2Regularization(0.0005 * 128) +) +``` + +`small_vgg`定义了网络结构。这里我们使用的是一个小的VGG网络。关于VGG卷积神经网络的描述可以参考:[http://www.robots.ox.ac.uk/~vgg/research/very_deep/](http://www.robots.ox.ac.uk/~vgg/research/very_deep/)。 +```python +# small_vgg is predined in trainer_config_helpers.network +predict = small_vgg(input_image=img, num_channels=3) +``` +配置创建完毕后,可以运行脚本train.sh来训练模型。 + +```bash +config=vgg_16_cifar.py +output=./cifar_vgg_model +log=train.log + +paddle train \ +--config=$config \ +--dot_period=10 \ +--log_period=100 \ +--test_all_data_in_one_period=1 \ +--use_gpu=1 \ +--save_dir=$output \ +2>&1 | tee $log + +python -m paddle.utils.plotcurve -i $log > plot.png +``` +- 这里我们使用的是GPU模式进行训练。如果你没有GPU环境,可以设置`use_gpu=0`。 +- `./demo/image_classification/vgg_16_cifar.py`是网络和数据配置文件。各项参数的详细说明可以在命令行参数相关文档中找到。 +- 脚本`plotcurve.py`依赖于python的`matplotlib`模块。因此如果这个脚本运行失败,也许是因为需要安装`matplotlib`。 +在训练完成后,训练及测试误差曲线图会被`plotcurve.py`脚本保存在 `plot.png`中。下面是一个误差曲线图的示例: + +
![Training and testing curves.](./plot.png)
+ +## 预测 +在训练完成后,模型及参数会被保存在路径`./cifar_vgg_model/pass-%05d`下。例如第300个pass的模型会被保存在`./cifar_vgg_model/pass-00299`。 + +要对一个图片的进行分类预测,我们可以使用`predict.sh`,该脚本将输出预测分类的标签: + +``` +sh predict.sh +``` + +predict.sh: +``` +model=cifar_vgg_model/pass-00299/ +image=data/cifar-out/test/airplane/seaplane_s_000978.png +use_gpu=1 +python prediction.py $model $image $use_gpu +``` + +## 练习 +在CUB-200数据集上使用VGG模型训练一个鸟类图片分类模型。相关的鸟类数据集可以从如下地址下载,其中包含了200种鸟类的照片(主要来自北美洲)。 + + + + + + +## 细节探究 +### 卷积神经网络 +卷积神经网络是一种使用卷积层的前向神经网络,很适合构建用于理解图片内容的模型。一个典型的神经网络如下图所示: + +![Convolutional Neural Network](./lenet.png) + +一个卷积神经网络包含如下层: + +- 卷积层:通过卷积操作从图片或特征图中提取特征 +- 池化层:使用max-pooling对特征图下采样 +- 全连接层:使输入层到隐藏层的神经元是全部连接的。 + +卷积神经网络在图片分类上有着惊人的性能,这是因为它发掘出了图片的两类重要信息:局部关联性质和空间不变性质。通过交替使用卷积和池化处理, 卷积神经网络能够很好的表示这两类信息。 + +关于如何定义网络中的层,以及如何在层之间进行连接,请参考Layer文档。 diff --git a/doc/tutorials/image_classification/index_en.md b/doc/tutorials/image_classification/index_en.md index 29cfc99702..60c81a6a53 100644 --- a/doc/tutorials/image_classification/index_en.md +++ b/doc/tutorials/image_classification/index_en.md @@ -147,7 +147,7 @@ for classification. A description of VGG network can be found here [http://www.r # small_vgg is predined in trainer_config_helpers.network predict = small_vgg(input_image=img, num_channels=3) ``` -After writing the config, we can train the model by running the script train.sh. Notice that the following script assumes the you run the script in the `./demo/image_classification` folder. If you run the script in a different folder, you need to change the paths of the scripts and the configuration files accordingly. +After writing the config, we can train the model by running the script train.sh. ```bash config=vgg_16_cifar.py diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 84a66719c3..5c4c25e770 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -20,15 +20,11 @@ limitations under the License. */ #include #include #include "paddle/utils/GlobalConstants.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" /// Import PaddlePaddle's enumeration into global namespace. using namespace paddle::enumeration_wrapper; // NOLINT -#define DISABLE_COPY_AND_ASSIGN(classname) \ - classname(const classname& other); \ - classname& operator=(const classname& other) - /** * @brief Initialize paddle. * @@ -102,7 +98,7 @@ const size_t NO_SPARSE_ID = -1UL; struct MatrixPrivate; class Matrix { Matrix(); // User Cannot Create Matrix. - DISABLE_COPY_AND_ASSIGN(Matrix); + DISABLE_COPY(Matrix); static Matrix* createByPaddleMatrixPtr(void* sharedPtr); public: @@ -242,7 +238,7 @@ private: struct VectorPrivate; class Vector { - DISABLE_COPY_AND_ASSIGN(Vector); + DISABLE_COPY(Vector); Vector(); static Vector* createByPaddleVectorPtr(void* ptr); @@ -322,7 +318,7 @@ private: struct IVectorPrivate; class IVector { IVector(); - DISABLE_COPY_AND_ASSIGN(IVector); + DISABLE_COPY(IVector); static IVector* createByPaddleVectorPtr(void* ptr); public: @@ -402,7 +398,7 @@ struct ArgumentsPrivate; class Arguments { private: Arguments(); // Internal Create. - DISABLE_COPY_AND_ASSIGN(Arguments); + DISABLE_COPY(Arguments); public: /** @@ -472,7 +468,7 @@ enum GradientMatchineCreateMode { struct ParameterConfigPrivate; class ParameterConfig { - DISABLE_COPY_AND_ASSIGN(ParameterConfig); + DISABLE_COPY(ParameterConfig); ParameterConfig(); /** @@ -502,7 +498,7 @@ private: struct OptimizationConfigPrivate; class OptimizationConfig { - DISABLE_COPY_AND_ASSIGN(OptimizationConfig); + DISABLE_COPY(OptimizationConfig); OptimizationConfig(); public: @@ -526,7 +522,7 @@ struct ParameterPrivate; class Parameter { private: Parameter(); - DISABLE_COPY_AND_ASSIGN(Parameter); + DISABLE_COPY(Parameter); public: virtual ~Parameter(); @@ -568,7 +564,7 @@ struct ModelConfigPrivate; class ModelConfig { private: ModelConfig(); - DISABLE_COPY_AND_ASSIGN(ModelConfig); + DISABLE_COPY(ModelConfig); public: virtual ~ModelConfig(); @@ -589,7 +585,7 @@ struct TrainerConfigPrivate; class TrainerConfig { private: TrainerConfig(); - DISABLE_COPY_AND_ASSIGN(TrainerConfig); + DISABLE_COPY(TrainerConfig); public: virtual ~TrainerConfig(); @@ -629,7 +625,7 @@ public: struct ParameterTraverseCallbackPrivate; class ParameterTraverseCallback { - DISABLE_COPY_AND_ASSIGN(ParameterTraverseCallback); + DISABLE_COPY(ParameterTraverseCallback); ParameterTraverseCallback(); public: @@ -651,7 +647,7 @@ private: */ struct ParameterOptimizerPrivate; class ParameterOptimizer { - DISABLE_COPY_AND_ASSIGN(ParameterOptimizer); + DISABLE_COPY(ParameterOptimizer); ParameterOptimizer(); public: @@ -688,7 +684,7 @@ struct GradientMachinePrivate; class GradientMachine { private: GradientMachine(); - DISABLE_COPY_AND_ASSIGN(GradientMachine); + DISABLE_COPY(GradientMachine); public: virtual ~GradientMachine(); @@ -780,7 +776,7 @@ private: TrainerPrivate* m; Trainer(); Trainer(TrainerConfig* optConfig, GradientMachine* gm); - DISABLE_COPY_AND_ASSIGN(Trainer); + DISABLE_COPY(Trainer); public: virtual ~Trainer(); @@ -846,7 +842,7 @@ public: struct SequenceGeneratorPrivate; class SequenceGenerator { - DISABLE_COPY_AND_ASSIGN(SequenceGenerator); + DISABLE_COPY(SequenceGenerator); SequenceGenerator(); public: diff --git a/paddle/cuda/include/hl_base.h b/paddle/cuda/include/hl_base.h index 84c5f2d5c9..5b9884b786 100644 --- a/paddle/cuda/include/hl_base.h +++ b/paddle/cuda/include/hl_base.h @@ -16,7 +16,31 @@ limitations under the License. */ #define HL_BASE_H_ #include -#include "paddle/utils/TypeDefs.h" + +#ifdef PADDLE_TYPE_DOUBLE +#define HL_FLOAT_MAX 3.40282347e+38F +#define HL_FLOAT_MIN 1.17549435e-38F +using real = double; +#else +#define HL_FLOAT_MAX 1.7976931348623157e+308 +#define HL_FLOAT_MIN 2.2250738585072014e-308 +using real = float; +#endif + +/** + * The maximum input value for exp, used to avoid overflow problem. + * currently only used for tanh function. + */ +#define EXP_MAX_INPUT 40.0 + +/** + * @brief DIVUP(x, y) is similar to ceil(x / y). + * @note For CUDA, DIVUP will be used to specify + * the size of blockDim. + */ +#ifndef DIVUP +#define DIVUP(x, y) (((x) + (y)-1) / (y)) +#endif /** * HPPL is an internal high performance parallel computing library @@ -181,46 +205,6 @@ typedef struct { size_t nnz; } _hl_sparse_matrix_s, *hl_sparse_matrix_s; -#ifndef PADDLE_TYPE_DOUBLE -/** - * HPPL data type: real (float or double) - * - * if real == float - * - * HL_FLOAT_MAX: 3.40282347e+38F - * - * HL_FLOAT_MIN: 1.17549435e-38F - */ -#define HL_FLOAT_MAX 3.40282347e+38F -/** - * if real == double - * - * HL_FLOAT_MAX: 1.7976931348623157e+308 - * - * HL_FLOAT_MIN: 2.2250738585072014e-308 - */ -#define HL_FLOAT_MIN 1.17549435e-38F -#else -#define HL_FLOAT_MAX 1.7976931348623157e+308 -#define HL_FLOAT_MIN 2.2250738585072014e-308 -#endif - -/** - * The maximum input value for exp, used to avoid overflow problem. - * - * Currently only used for tanh function. - */ -#define EXP_MAX_INPUT 40.0 - -/** - * @brief DIVUP(x, y) is similar to ceil(x / y). - * @note For CUDA, DIVUP will be used to specify - * the size of blockDim. - */ -#ifndef DIVUP -#define DIVUP(x, y) (((x) + (y)-1) / (y)) -#endif - #ifdef __NVCC__ #include "cuda_runtime.h" diff --git a/paddle/gserver/dataproviders/DataProvider.h b/paddle/gserver/dataproviders/DataProvider.h index 9b7f7e36ce..5f031fc7c0 100644 --- a/paddle/gserver/dataproviders/DataProvider.h +++ b/paddle/gserver/dataproviders/DataProvider.h @@ -34,8 +34,8 @@ limitations under the License. */ #include "paddle/utils/Logging.h" #include "paddle/utils/Queue.h" #include "paddle/utils/ThreadLocal.h" -#include "paddle/utils/TypeDefs.h" #include "paddle/utils/Util.h" +#include "paddle/utils/common.h" namespace paddle { /** diff --git a/paddle/gserver/layers/BatchNormalizationLayer.h b/paddle/gserver/layers/BatchNormalizationLayer.h index 052c207732..195acbbfc5 100644 --- a/paddle/gserver/layers/BatchNormalizationLayer.h +++ b/paddle/gserver/layers/BatchNormalizationLayer.h @@ -58,6 +58,8 @@ protected: /// to batch, channels* imagePixels. void shrinkMat(const MatrixPtr& in, MatrixPtr& out); + void onPassEnd() { firstTest_ = true; } + MatrixPtr tmpMat_, tmpGrad_; MatrixPtr expandedIn_, expandedOut_; MatrixPtr expandedInGrad_, expandedOutGrad_, inGrad_; diff --git a/paddle/gserver/layers/GruCompute.h b/paddle/gserver/layers/GruCompute.h index 42c0019319..a56af21317 100644 --- a/paddle/gserver/layers/GruCompute.h +++ b/paddle/gserver/layers/GruCompute.h @@ -16,7 +16,7 @@ limitations under the License. */ #include "ModelConfig.pb.h" #include "hl_gpu.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/gserver/layers/LstmCompute.h b/paddle/gserver/layers/LstmCompute.h index 140a4c6ecf..0d65b4158e 100644 --- a/paddle/gserver/layers/LstmCompute.h +++ b/paddle/gserver/layers/LstmCompute.h @@ -16,7 +16,7 @@ limitations under the License. */ #include "ModelConfig.pb.h" #include "hl_gpu.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/gserver/layers/MultinomialSampler.h b/paddle/gserver/layers/MultinomialSampler.h index 677b047029..b48073c80b 100644 --- a/paddle/gserver/layers/MultinomialSampler.h +++ b/paddle/gserver/layers/MultinomialSampler.h @@ -16,7 +16,7 @@ limitations under the License. */ #include #include -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/math/BaseMatrix.h b/paddle/math/BaseMatrix.h index 2933c20fba..8f9bc9e823 100644 --- a/paddle/math/BaseMatrix.h +++ b/paddle/math/BaseMatrix.h @@ -16,7 +16,7 @@ limitations under the License. */ #include #include #include "TensorExpression.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index 25ce09e346..bda863de38 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -27,7 +27,7 @@ limitations under the License. */ #include "MemoryHandle.h" #include "Vector.h" #include "paddle/utils/ThreadLocal.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/math/TensorExpression.h b/paddle/math/TensorExpression.h index 9bd789e8c5..f3d60e4003 100644 --- a/paddle/math/TensorExpression.h +++ b/paddle/math/TensorExpression.h @@ -17,7 +17,7 @@ limitations under the License. */ #include #include "hl_tensor_ops.h" #include "paddle/utils/Logging.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/math/Vector.h b/paddle/math/Vector.h index 8a24103bd4..b4347a70f8 100644 --- a/paddle/math/Vector.h +++ b/paddle/math/Vector.h @@ -22,7 +22,7 @@ limitations under the License. */ #include "BaseMatrix.h" #include "MemoryHandle.h" #include "paddle/utils/Thread.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/parameter/ParallelParameter.h b/paddle/parameter/ParallelParameter.h index 417e386dc7..1ee220d2dc 100644 --- a/paddle/parameter/ParallelParameter.h +++ b/paddle/parameter/ParallelParameter.h @@ -28,7 +28,7 @@ limitations under the License. */ #include "paddle/parameter/ParameterUpdateFunctions.h" #include "paddle/utils/Flags.h" #include "paddle/utils/Locks.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" #include "ParameterConfig.pb.h" diff --git a/paddle/parameter/Parameter.h b/paddle/parameter/Parameter.h index 532c6770e5..e05137b315 100644 --- a/paddle/parameter/Parameter.h +++ b/paddle/parameter/Parameter.h @@ -29,8 +29,8 @@ limitations under the License. */ #include "paddle/utils/GlobalConstants.h" #include "paddle/utils/Locks.h" #include "paddle/utils/ThreadLocal.h" -#include "paddle/utils/TypeDefs.h" #include "paddle/utils/Util.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/parameter/ParameterUpdateFunctions.h b/paddle/parameter/ParameterUpdateFunctions.h index 2d277e47e7..2cb3798717 100644 --- a/paddle/parameter/ParameterUpdateFunctions.h +++ b/paddle/parameter/ParameterUpdateFunctions.h @@ -15,7 +15,7 @@ limitations under the License. */ #pragma once #include "paddle/math/Vector.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/pserver/BaseClient.h b/paddle/pserver/BaseClient.h index 262afafbe2..ccf05ae1ca 100644 --- a/paddle/pserver/BaseClient.h +++ b/paddle/pserver/BaseClient.h @@ -18,7 +18,7 @@ limitations under the License. */ #include "paddle/math/Matrix.h" #include "paddle/pserver/ProtoServer.h" #include "paddle/utils/Queue.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/pserver/ParameterClient2.h b/paddle/pserver/ParameterClient2.h index eed71ccb43..70cfc6d700 100644 --- a/paddle/pserver/ParameterClient2.h +++ b/paddle/pserver/ParameterClient2.h @@ -26,8 +26,8 @@ limitations under the License. */ #include "paddle/utils/Flags.h" #include "paddle/utils/Locks.h" #include "paddle/utils/Queue.h" -#include "paddle/utils/TypeDefs.h" #include "paddle/utils/Util.h" +#include "paddle/utils/common.h" #include "ParameterService.pb.h" diff --git a/paddle/pserver/ParameterServer2.h b/paddle/pserver/ParameterServer2.h index b0cf22e1fb..79d1eb97ff 100644 --- a/paddle/pserver/ParameterServer2.h +++ b/paddle/pserver/ParameterServer2.h @@ -32,7 +32,7 @@ limitations under the License. */ #include "paddle/utils/Locks.h" #include "paddle/utils/Stat.h" #include "paddle/utils/ThreadLocal.h" -#include "paddle/utils/TypeDefs.h" +#include "paddle/utils/common.h" #include "ParameterService.pb.h" diff --git a/paddle/utils/CpuId.h b/paddle/utils/CpuId.h index 7a354da758..1218e8194c 100644 --- a/paddle/utils/CpuId.h +++ b/paddle/utils/CpuId.h @@ -11,7 +11,7 @@ limitations under the License. */ #pragma once -#include "DisableCopy.h" +#include "common.h" namespace paddle { diff --git a/paddle/utils/DisableCopy.h b/paddle/utils/DisableCopy.h deleted file mode 100644 index 41de98bbde..0000000000 --- a/paddle/utils/DisableCopy.h +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -/** - * Disable copy macro. - */ -#define DISABLE_COPY(CLASS_NAME) \ - CLASS_NAME(CLASS_NAME &&) = delete; \ - CLASS_NAME(const CLASS_NAME &other) = delete; \ - CLASS_NAME &operator=(const CLASS_NAME &other) = delete diff --git a/paddle/utils/Locks.h b/paddle/utils/Locks.h index 0f922f3548..a21872e89e 100644 --- a/paddle/utils/Locks.h +++ b/paddle/utils/Locks.h @@ -19,7 +19,7 @@ limitations under the License. */ #include #include -#include "DisableCopy.h" +#include "common.h" namespace paddle { diff --git a/paddle/utils/Util.h b/paddle/utils/Util.h index e5a89070f1..dc15ada586 100644 --- a/paddle/utils/Util.h +++ b/paddle/utils/Util.h @@ -26,12 +26,11 @@ limitations under the License. */ #include #include -#include "DisableCopy.h" #include "Logging.h" #include "TrainerConfig.pb.h" +#include "common.h" #include "Flags.h" -#include "TypeDefs.h" #include "hl_gpu.h" /** diff --git a/paddle/utils/Version.h b/paddle/utils/Version.h index d1a07d9485..aa5df32438 100644 --- a/paddle/utils/Version.h +++ b/paddle/utils/Version.h @@ -15,7 +15,7 @@ limitations under the License. */ #pragma once #include #include -#include "TypeDefs.h" +#include "common.h" namespace paddle { diff --git a/paddle/utils/TypeDefs.h b/paddle/utils/common.h similarity index 71% rename from paddle/utils/TypeDefs.h rename to paddle/utils/common.h index c50a05e82d..3ff0b86947 100644 --- a/paddle/utils/TypeDefs.h +++ b/paddle/utils/common.h @@ -15,12 +15,19 @@ limitations under the License. */ #pragma once namespace paddle { + +/** + * Disable copy macro. + */ +#define DISABLE_COPY(class_name) \ + class_name(class_name &&) = delete; \ + class_name(const class_name &other) = delete; \ + class_name &operator=(const class_name &other) = delete + #ifdef PADDLE_TYPE_DOUBLE -typedef double real; +using real = double; #else -typedef float real; +using real = float; #endif } // namespace paddle - -using paddle::real; From b132fde01de8352b43fb8f68f308c232eea91212 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 23 Dec 2016 15:20:12 +0800 Subject: [PATCH 043/119] follow some comments with issue 973 --- paddle/function/CMakeLists.txt | 10 +++++----- .../{cross_map_normal_op.cpp => CrossMapNormalOp.cpp} | 2 +- .../{cross_map_normal_op.h => CrossMapNormalOp.h} | 0 ...oss_map_normal_op_gpu.cu => CrossMapNormalOpGpu.cu} | 2 +- ...map_normal_op_test.cpp => CrossMapNormalOpTest.cpp} | 4 ++++ paddle/function/FunctionTest.h | 5 ----- 6 files changed, 11 insertions(+), 12 deletions(-) rename paddle/function/{cross_map_normal_op.cpp => CrossMapNormalOp.cpp} (99%) rename paddle/function/{cross_map_normal_op.h => CrossMapNormalOp.h} (100%) rename paddle/function/{cross_map_normal_op_gpu.cu => CrossMapNormalOpGpu.cu} (99%) rename paddle/function/{cross_map_normal_op_test.cpp => CrossMapNormalOpTest.cpp} (98%) diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 0697842bbe..4660324423 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -1,11 +1,11 @@ -file(GLOB h_files . *_op.h) -file(GLOB cpp_files . *_op.cpp) +file(GLOB h_files . *Op.h) +file(GLOB cpp_files . *Op.cpp) list(APPEND h_files Function.h) list(APPEND cpp_files Function.cpp) if(WITH_GPU) - file(GLOB cu_files . *_op_gpu.cu) + file(GLOB cu_files . *OpGpu.cu) cuda_compile(cu_objs ${cu_files}) endif() @@ -15,9 +15,9 @@ add_library(paddle_test_main STATIC TestMain.cpp) if(WITH_GPU) # TODO: - # file(GLOB test_files . *_op_test.cpp) + # file(GLOB test_files . *OpTest.cpp) # add_executable(${test_bin} EXCLUDE_FROM_ALL ${test_files}) - add_simple_unittest(cross_map_normal_op_test) + add_simple_unittest(CrossMapNormalOpTest) endif() add_style_check_target(paddle_function ${h_files}) diff --git a/paddle/function/cross_map_normal_op.cpp b/paddle/function/CrossMapNormalOp.cpp similarity index 99% rename from paddle/function/cross_map_normal_op.cpp rename to paddle/function/CrossMapNormalOp.cpp index a9c7693830..f13eb78d27 100644 --- a/paddle/function/cross_map_normal_op.cpp +++ b/paddle/function/CrossMapNormalOp.cpp @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -#include "cross_map_normal_op.h" +#include "CrossMapNormalOp.h" #include "paddle/math/Vector.h" namespace paddle { diff --git a/paddle/function/cross_map_normal_op.h b/paddle/function/CrossMapNormalOp.h similarity index 100% rename from paddle/function/cross_map_normal_op.h rename to paddle/function/CrossMapNormalOp.h diff --git a/paddle/function/cross_map_normal_op_gpu.cu b/paddle/function/CrossMapNormalOpGpu.cu similarity index 99% rename from paddle/function/cross_map_normal_op_gpu.cu rename to paddle/function/CrossMapNormalOpGpu.cu index aae4f461b6..b33dd10834 100644 --- a/paddle/function/cross_map_normal_op_gpu.cu +++ b/paddle/function/CrossMapNormalOpGpu.cu @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "hl_base.h" -#include "cross_map_normal_op.h" +#include "CrossMapNormalOp.h" namespace paddle { diff --git a/paddle/function/cross_map_normal_op_test.cpp b/paddle/function/CrossMapNormalOpTest.cpp similarity index 98% rename from paddle/function/cross_map_normal_op_test.cpp rename to paddle/function/CrossMapNormalOpTest.cpp index 22692691bd..d65d9310af 100644 --- a/paddle/function/cross_map_normal_op_test.cpp +++ b/paddle/function/CrossMapNormalOpTest.cpp @@ -15,6 +15,8 @@ limitations under the License. */ #include #include "FunctionTest.h" +namespace paddle { + TEST(CrossMapNormal, real) { for (size_t numSamples : {5, 32}) { for (size_t channels : {1, 5, 32}) { @@ -69,3 +71,5 @@ TEST(CrossMapNormalGrad, real) { } } } + +} // namespace paddle diff --git a/paddle/function/FunctionTest.h b/paddle/function/FunctionTest.h index a8c5e412bd..a602bde57e 100644 --- a/paddle/function/FunctionTest.h +++ b/paddle/function/FunctionTest.h @@ -95,8 +95,3 @@ protected: }; } // namespace paddle - -using paddle::FunctionCompare; -using paddle::FuncConfig; -using paddle::Dims; -using paddle::Tensor; From dc1ee4449c9ef1c53b5e6969bf06e5eff884da91 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Fri, 23 Dec 2016 15:38:19 +0800 Subject: [PATCH 044/119] follow some comments whit pr 854 after committed --- paddle/function/Function.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paddle/function/Function.cpp b/paddle/function/Function.cpp index 02880e5ea1..eb005e6744 100644 --- a/paddle/function/Function.cpp +++ b/paddle/function/Function.cpp @@ -32,14 +32,14 @@ real FuncConfig::get(const std::string& key) const { template <> FuncConfig& FuncConfig::set(const std::string& key, size_t v) { - CHECK(valueMap_.count(key) == 0) << "Duplicated value: " << key; + CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; valueMap_[key].s = v; return *this; } template <> FuncConfig& FuncConfig::set(const std::string& key, real v) { - CHECK(valueMap_.count(key) == 0) << "Duplicated value: " << key; + CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; valueMap_[key].r = v; return *this; } From e2265745ee85d57b77fc399ac1ed3910bfd32adb Mon Sep 17 00:00:00 2001 From: CrossLee1 Date: Fri, 23 Dec 2016 16:01:10 +0800 Subject: [PATCH 045/119] Update arguments_cn.md --- doc/howto/usage/cmd_parameter/arguments_cn.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/howto/usage/cmd_parameter/arguments_cn.md b/doc/howto/usage/cmd_parameter/arguments_cn.md index 777cac7f93..833e21dd19 100644 --- a/doc/howto/usage/cmd_parameter/arguments_cn.md +++ b/doc/howto/usage/cmd_parameter/arguments_cn.md @@ -244,7 +244,7 @@ -参数服务(PServer)start_pserver +参数服务器(PServer)start_pserver √√ @@ -379,7 +379,7 @@ -数据提供(Data Provider)memory_threshold_on_load_data +数据提供器(Data Provider)memory_threshold_on_load_data √√ From 72436e6f29ad3162798e6a05a3d789e4e0d3f0e4 Mon Sep 17 00:00:00 2001 From: CrossLee1 Date: Fri, 23 Dec 2016 16:09:50 +0800 Subject: [PATCH 046/119] Update detail_introduction_cn.md --- .../cmd_parameter/detail_introduction_cn.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/howto/usage/cmd_parameter/detail_introduction_cn.md b/doc/howto/usage/cmd_parameter/detail_introduction_cn.md index 1e6f32eb49..dbf7c6f00b 100644 --- a/doc/howto/usage/cmd_parameter/detail_introduction_cn.md +++ b/doc/howto/usage/cmd_parameter/detail_introduction_cn.md @@ -15,7 +15,7 @@ - 类型: bool (默认: 1). * `--local` - - 训练过程是否为本地模式,设置为true使用本地训练或者使用集群上的一个节点,否则使用集群上的多台机器。 +  - 训练过程是否为本地模式,设置为true使用本地训练或者使用集群上的一个节点,否则使用多机训练。 - 类型: bool (默认: 1). * `--trainer_count` @@ -114,11 +114,11 @@ - 类型: int32 (默认: 0). * `--test_wait` - - 指示当指定轮的测试模型不存在时,是否需要等待该轮模型参数。如果在集群的提交环境中设置了test_data_path,将会启动一个进程执行测试,因此我们需要设置test_wait=1。需要注意的是,在集群提交环境中,该参数已经默认设置为True. + - 指示当指定轮的测试模型不存在时,是否需要等待该轮模型参数。如果在训练期间同时发起另外一个进程进行测试,可以使用该参数. - 类型: bool (默认: 0). * `--model_list` - - 测试时指定的存储模型列表的文件。在集群提交环境中,在指定model_path之后,该参数会自动设置. + - 测试时指定的存储模型列表的文件. - 类型: string (默认: "", null). * `--predict_output_dir` @@ -169,7 +169,7 @@ - 类型: bool (默认: 0). * `--prev_batch_state` - - 指示批次之间是否是连续的. + - 标识是否为连续的batch计算. - 类型: bool (默认: 0). * `--beam_size` @@ -177,7 +177,7 @@ - 类型: int32 (默认: 1). * `--diy_beam_search_prob_so` - - 指定共享的动态库,用户可以定义Paddle之外的文件. +  - 用户可以自定义beam search的方法,编译成动态库,供PaddlePaddle加载。 该参数用于指定动态库路径. - 类型: string (默认: "", null). ## 度量学习(Metric Learning) @@ -195,20 +195,20 @@ - 内存容限阈值,当超过该阈值时,停止加载数据. - 类型: double (默认: 1.0). -## Unit Test +## 单元测试 * `--checkgrad_eps` - 使用checkgrad模式时的参数变化大小. - 类型: double (默认: 1e-05). -## 参数服务器(Parameter Server)和分布式通信(Distributed Communication) +## 参数服务器和分布式通信 * `--start_pserver` - 指示是否开启参数服务器(parameter server). - 类型: bool (默认: 0). * `--pservers` - - 参数服务器的IP地址,以逗号间隔,在集群提交环境中自动设置. + - 参数服务器的IP地址,以逗号间隔. - 类型: string (默认: "127.0.0.1"). * `--port` @@ -220,7 +220,7 @@ - 类型: int32 (默认: 1). * `--trainer_id` - - 在分布式训练中,每个训练器必须指定一个唯一的id号,从0到num_trainers-1。0号训练器是主训练器。使用者无需关心这个参数. +  - 在分布式训练中,每个训练节点必须指定一个唯一的id号,从0到num_trainers-1。0号训练节点是主训练节点。使用者无需关心这个参数. - 类型: int32 (默认: 0). * `--num_gradient_servers` From 02be4fe5ed4491ad7912370825932ff074de3f2f Mon Sep 17 00:00:00 2001 From: CrossLee1 Date: Fri, 23 Dec 2016 16:14:51 +0800 Subject: [PATCH 047/119] Update detail_introduction_en.md --- doc/howto/usage/cmd_parameter/detail_introduction_en.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/howto/usage/cmd_parameter/detail_introduction_en.md b/doc/howto/usage/cmd_parameter/detail_introduction_en.md index c4136adeba..aa69a3bd54 100644 --- a/doc/howto/usage/cmd_parameter/detail_introduction_en.md +++ b/doc/howto/usage/cmd_parameter/detail_introduction_en.md @@ -118,11 +118,11 @@ - type: int32 (default: 0). * `--test_wait` - - Whether to wait for parameter per pass if not exist. If set test_data_path in submitting environment of cluster, it will launch one process to perfom testing, so we need to set test_wait=1. Note that in the cluster submitting environment, this argument has been set True by default. +  - Whether to wait for parameter per pass if not exist. It can be used when user launch another process to perfom testing during the training process. - type: bool (default: 0). * `--model_list` - - File that saves the model list when testing. It was set automatically when using cluster submitting environment after setting model_path. + - File that saves the model list when testing. - type: string (default: "", null). * `--predict_output_dir` @@ -212,7 +212,7 @@ - type: bool (default: 0). * `--pservers` - - Comma separated IP addresses of pservers. It is set automatically in cluster submitting environment. + - Comma separated IP addresses of pservers. - type: string (default: "127.0.0.1"). * `--port` From 027aaf9ef26ad89b33a2d094cb8196926a911cc2 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Sun, 25 Dec 2016 19:32:36 +0800 Subject: [PATCH 048/119] add cluster train for quick_start --- demo/quick_start/api_predict.sh | 2 +- demo/quick_start/cluster/cluster_train.sh | 44 +++++++++++++++++++++++ demo/quick_start/cluster/env.sh | 28 +++++++++++++++ demo/quick_start/cluster/pserver.sh | 26 ++++++++++++++ paddle/trainer/ThreadParameterUpdater.h | 4 +-- 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100755 demo/quick_start/cluster/cluster_train.sh create mode 100644 demo/quick_start/cluster/env.sh create mode 100755 demo/quick_start/cluster/pserver.sh diff --git a/demo/quick_start/api_predict.sh b/demo/quick_start/api_predict.sh index c90d3b7054..4d9aa9e885 100755 --- a/demo/quick_start/api_predict.sh +++ b/demo/quick_start/api_predict.sh @@ -17,7 +17,7 @@ set -e #Note the default model is pass-00002, you shold make sure the model path #exists or change the mode path. #only test on trainer_config.lr.py -model=output/pass-00001/ +model=output/model/pass-00001/ config=trainer_config.lr.py label=data/labels.list dict=data/dict.txt diff --git a/demo/quick_start/cluster/cluster_train.sh b/demo/quick_start/cluster/cluster_train.sh new file mode 100755 index 0000000000..aac9b89b14 --- /dev/null +++ b/demo/quick_start/cluster/cluster_train.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e + +# Should run pserver.sh before run this script. +bin_dir=$(cd `dirname $0`; pwd) +home_dir=$(cd "${bin_dir}/.."; pwd) +source "$bin_dir/env.sh" + +model_dir="$bin_dir/output" +log_file="$bin_dir/train.log" + +pushd "$home_dir" +cfg=trainer_config.lr.py +paddle train \ + --config=$cfg \ + --save_dir=${model_dir} \ + --trainer_count=4 \ + --local=0 \ + --log_period=100 \ + --num_passes=15 \ + --use_gpu=false \ + --show_parameter_stats_period=100 \ + --test_all_data_in_one_period=1 \ + --num_gradient_servers=1 \ + --nics=`get_nics` \ + --port=7164 \ + --ports_num=1 \ + --pservers="127.0.0.1" \ + --comment="paddle_trainer" \ + 2>&1 | tee "$log_file" +popd diff --git a/demo/quick_start/cluster/env.sh b/demo/quick_start/cluster/env.sh new file mode 100644 index 0000000000..a404993835 --- /dev/null +++ b/demo/quick_start/cluster/env.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e + +function get_nics() { + machine=`uname -s` + local nics="" + if [ "$machine" == "Linux" ]; then + nics="lo" + elif [ "$machine" == "Darwin" ]; then + nics="lo0" + else + nics="unsupport" + fi + echo $nics +} diff --git a/demo/quick_start/cluster/pserver.sh b/demo/quick_start/cluster/pserver.sh new file mode 100755 index 0000000000..b187c1d9b9 --- /dev/null +++ b/demo/quick_start/cluster/pserver.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e +bin_dir=$(cd `dirname $0`; pwd) +source "$bin_dir/env.sh" + +paddle pserver \ + --nics=`get_nics` \ + --port=7164 \ + --ports_num=1 \ + --ports_num_for_sparse=1 \ + --num_gradient_servers=1 \ + --comment="paddle_pserver" \ + 2>&1 | tee 'pserver.log' diff --git a/paddle/trainer/ThreadParameterUpdater.h b/paddle/trainer/ThreadParameterUpdater.h index 880f1f9ddc..bc08a9e9f0 100644 --- a/paddle/trainer/ThreadParameterUpdater.h +++ b/paddle/trainer/ThreadParameterUpdater.h @@ -33,8 +33,8 @@ namespace paddle { because at the current moment, the merging on CPU is happening on the main thread, and the its parameter size can be much larger than the one GPU. Thus, for GPU, the parameter updates happens in updateImpl() function, which - is called by gradient machines as a callback function as a callback function - supplied to backward() and forwardBackward(). + is called by gradient machines as a callback function supplied to backward() + and forwardBackward(). For CPU, the parameter updates happens in separate threads maintained by this class. */ From 8a2ceda67659f52a83212ca15aff7b057e63d50c Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 10:48:37 +0800 Subject: [PATCH 049/119] Add externel google's dependencies --- cmake/external/gflags.cmake | 39 ++++++++++++++++++++++++++ cmake/external/glog.cmake | 40 +++++++++++++++++++++++++++ cmake/external/gtest.cmake | 48 ++++++++++++++++++++++++++++++++ cmake/external/protobuf.cmake | 52 +++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 cmake/external/gflags.cmake create mode 100644 cmake/external/glog.cmake create mode 100644 cmake/external/gtest.cmake create mode 100644 cmake/external/protobuf.cmake diff --git a/cmake/external/gflags.cmake b/cmake/external/gflags.cmake new file mode 100644 index 0000000000..128d50cec8 --- /dev/null +++ b/cmake/external/gflags.cmake @@ -0,0 +1,39 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(ExternalProject) + +SET(GFLAGS_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gflags) +SET(GFLAGS_INSTALL_DIR ${PROJECT_BINARY_DIR}/gflags) + +ExternalProject_Add( + gflags + GIT_REPOSITORY "https://github.com/gflags/gflags.git" + PREFIX ${GFLAGS_SOURCES_DIR} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${GFLAGS_INSTALL_DIR} + CMAKE_ARGS -DBUILD_TESTING=OFF + LOG_DOWNLOAD =ON + UPDATE_COMMAND "" +) + +SET(GFLAGS_INCLUDE_DIR "${GFLAGS_INSTALL_DIR}/include" CACHE PATH "gflags include directory." FORCE) +INCLUDE_DIRECTORIES(${GFLAGS_INCLUDE_DIR}) + +IF(WIN32) + set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/gflags.lib" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE) +ELSE(WIN32) + set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/libgflags.a" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE) +ENDIF(WIN32) + +LIST(APPEND external_project_dependencies gflags) diff --git a/cmake/external/glog.cmake b/cmake/external/glog.cmake new file mode 100644 index 0000000000..8a4b9d5996 --- /dev/null +++ b/cmake/external/glog.cmake @@ -0,0 +1,40 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(ExternalProject) + +SET(GLOG_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/glog) +SET(GLOG_INSTALL_DIR ${PROJECT_BINARY_DIR}/glog) + +ExternalProject_Add( + glog + GIT_REPOSITORY "https://github.com/google/glog.git" + PREFIX ${GLOG_SOURCES_DIR} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${GLOG_INSTALL_DIR} + CMAKE_ARGS -DWITH_GFLAGS=OFF + CMAKE_ARGS -DBUILD_TESTING=OFF + LOG_DOWNLOAD =ON + UPDATE_COMMAND "" +) + +SET(GLOG_INCLUDE_DIR "${GLOG_INSTALL_DIR}/include" CACHE PATH "glog include directory." FORCE) +INCLUDE_DIRECTORIES(${GLOG_INCLUDE_DIR}) + +IF(WIN32) + SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.lib" CACHE FILEPATH "glog library." FORCE) +ELSE(WIN32) + SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.a" CACHE FILEPATH "glog library." FORCE) +ENDIF(WIN32) + +LIST(APPEND external_project_dependencies glog) diff --git a/cmake/external/gtest.cmake b/cmake/external/gtest.cmake new file mode 100644 index 0000000000..533104422a --- /dev/null +++ b/cmake/external/gtest.cmake @@ -0,0 +1,48 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(ExternalProject) + +SET(GTEST_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gtest) +SET(GTEST_INSTALL_DIR ${PROJECT_BINARY_DIR}/gtest) + +ExternalProject_Add( + gtest + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "release-1.8.0" + PREFIX ${GTEST_SOURCES_DIR} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GTEST_INSTALL_DIR} + CMAKE_ARGS -DBUILD_GMOCK=ON + CMAKE_ARGS -Dgtest_disable_pthreads=ON + CMAKE_ARGS -Dgtest_force_shared_crt=ON + LOG_DOWNLOAD =ON + UPDATE_COMMAND "" +) + +SET(GTEST_INCLUDE_DIR "${GTEST_INSTALL_DIR}/include" CACHE PATH "gtest include directory." FORCE) +INCLUDE_DIRECTORIES(${GTEST_INCLUDE_DIR}) + +IF(WIN32) + set(GTEST_LIBRARIES + "${GTEST_INSTALL_DIR}/lib/gtest.lib" + "${GTEST_INSTALL_DIR}/lib/gtest_main.lib" CACHE FILEPATH "gtest libraries." FORCE) +ELSE(WIN32) + set(GTEST_LIBRARIES + "${GTEST_INSTALL_DIR}/lib/libgtest.a" + "${GTEST_INSTALL_DIR}/lib/libgtest_main.a" CACHE FILEPATH "gtest libraries." FORCE) +ENDIF(WIN32) + +ENABLE_TESTING() + +LIST(APPEND external_project_dependencies gtest) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake new file mode 100644 index 0000000000..8acc6325b9 --- /dev/null +++ b/cmake/external/protobuf.cmake @@ -0,0 +1,52 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(ExternalProject) + +SET(PROTOBUF_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/protobuf) +SET(PROTOBUF_INSTALL_DIR ${PROJECT_BINARY_DIR}/protobuf) + +ExternalProject_Add( + protobuf + PREFIX ${PROTOBUF_SOURCES_DIR} + DEPENDS zlib + GIT_REPOSITORY "https://github.com/google/protobuf.git" + GIT_TAG "v3.0.0" + CONFIGURE_COMMAND + ${CMAKE_COMMAND} ${PROTOBUF_SOURCES_DIR}/src/protobuf/cmake + -Dprotobuf_BUILD_TESTS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_INSTALL_PREFIX=${PROTOBUF_INSTALL_DIR} + UPDATE_COMMAND "" +) + +SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" CACHE PATH "protobuf include directory." FORCE) +INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) + +IF(WIN32) + SET(PROTOBUF_LIBRARIES + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite.lib" + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf.lib" + "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.lib" CACHE FILEPATH "protobuf libraries." FORCE) + SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc.exe" CACHE FILEPATH "protobuf executable." FORCE) +ELSE(WIN32) + SET(PROTOBUF_LIBRARIES + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite.a" + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf.a" + "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.a" CACHE FILEPATH "protobuf libraries." FORCE) + SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc" CACHE FILEPATH "protobuf executable." FORCE) +ENDIF(WIN32) + +LIST(APPEND external_project_dependencies protobuf) From 280994fafac08ac77e22b796aced5535fdc3a009 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 11:25:28 +0800 Subject: [PATCH 050/119] Add other extern dependencies --- cmake/external/numpy.cmake | 57 ++++++++++++++++++++++++ cmake/external/openblas.cmake | 44 +++++++++++++++++++ cmake/external/python.cmake | 83 +++++++++++++++++++++++++++++++++++ cmake/external/swig.cmake | 75 +++++++++++++++++++++++++++++++ cmake/external/warpctc.cmake | 39 ++++++++++++++++ cmake/external/zlib.cmake | 44 +++++++++++++++++++ 6 files changed, 342 insertions(+) create mode 100644 cmake/external/numpy.cmake create mode 100644 cmake/external/openblas.cmake create mode 100644 cmake/external/python.cmake create mode 100644 cmake/external/swig.cmake create mode 100644 cmake/external/warpctc.cmake create mode 100644 cmake/external/zlib.cmake diff --git a/cmake/external/numpy.cmake b/cmake/external/numpy.cmake new file mode 100644 index 0000000000..de3e6492cd --- /dev/null +++ b/cmake/external/numpy.cmake @@ -0,0 +1,57 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(ExternalProject) + +SET(NUMPY_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/numpy) +SET(NUMPY_INSTALL_DIR ${PROJECT_BINARY_DIR}/numpy) +set(NUMPY_VERSION "v1.11.3") + +# setuptools +ExternalProject_Add(setuptools + PREFIX ${PYTHON_SOURCES_DIR}/setuptools + URL http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz + URL_MD5 7df2a529a074f613b509fb44feefe74e + BUILD_IN_SOURCE 1 + UPDATE_COMMAND "" + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python zlib +) + +ExternalProject_Add(cython + PREFIX ${PYTHON_SOURCES_DIR}/cython + GIT_REPOSITORY https://github.com/cython/cython.git + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND "" + UPDATE_COMMAND "" + PATCH_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python +) + +ExternalProject_Add(numpy + GIT_REPOSITORY https://github.com/numpy/numpy.git + GIT_TAG ${NUMPY_VERSION} + CONFIGURE_COMMAND "" + UPDATE_COMMAND "" + PREFIX ${NUMPY_SOURCES_DIR} + BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py build + INSTALL_COMMAND ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools cython +) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake new file mode 100644 index 0000000000..d1220036a0 --- /dev/null +++ b/cmake/external/openblas.cmake @@ -0,0 +1,44 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# INCLUDE(cblas) + +INCLUDE(ExternalProject) + +SET(CBLAS_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/openblas) +SET(CBLAS_INSTALL_DIR ${PROJECT_BINARY_DIR}/openblas) + +ExternalProject_Add( + openblas + GIT_REPOSITORY "https://github.com/xianyi/OpenBLAS.git" + GIT_TAG v0.2.19 + PREFIX ${CBLAS_SOURCES_DIR} + INSTALL_DIR ${CBLAS_INSTALL_DIR} + BUILD_IN_SOURCE 1 + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND cd ${CBLAS_SOURCES_DIR}/src/openblas && make -j4 + INSTALL_COMMAND cd ${CBLAS_SOURCES_DIR}/src/openblas && make install PREFIX= +) + +SET(CBLAS_INCLUDE_DIR "${CBLAS_INSTALL_DIR}/include" CACHE PATH "openblas include directory." FORCE) +INCLUDE_DIRECTORIES(${CBLAS_INCLUDE_DIR}) + +IF(WIN32) + set(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/openblas.lib" CACHE FILEPATH "openblas library." FORCE) +ELSE(WIN32) + set(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/libopenblas.a" CACHE FILEPATH "openblas library" FORCE) +ENDIF(WIN32) + +LIST(APPEND external_project_dependencies openblas) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake new file mode 100644 index 0000000000..b459913314 --- /dev/null +++ b/cmake/external/python.cmake @@ -0,0 +1,83 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(ExternalProject) + +SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/python) +SET(PYTHON_INSTALL_DIR ${PROJECT_BINARY_DIR}/python) + +if(MSVC) + list(APPEND EXTERNAL_PROJECT_OPTIONAL_ARGS + PATCH_COMMAND ${CMAKE_COMMAND} + -DPYTHON_SRC_DIR:PATH=${_python_SOURCE_DIR} + -P ${CMAKE_CURRENT_LIST_DIR}/PythonPatch.cmake + ) +endif() + +if(APPLE) + list(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS + -DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON + ) +endif() + +set(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) + +# Force Python build to "Release". +if(CMAKE_CONFIGURATION_TYPES) + set(SAVED_CMAKE_CFG_INTDIR ${CMAKE_CFG_INTDIR}) + set(CMAKE_CFG_INTDIR "Release") +else() + list(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS + -DCMAKE_BUILD_TYPE:STRING=Release) +endif() + +ExternalProject_Add(python + GIT_REPOSITORY "https://github.com/python-cmake-buildsystem/python-cmake-buildsystem.git" + GIT_TAG "ed5f9bcee540e47f82fa17f8360b820591aa6d66" + PREFIX ${PYTHON_SOURCES_DIR} + UPDATE_COMMAND "" + CMAKE_CACHE_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${PYTHON_INSTALL_DIR} + -DBUILD_SHARED:BOOL=OFF + -DBUILD_STATIC:BOOL=ON + -DUSE_SYSTEM_LIBRARIES:BOOL=OFF + -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} + -DZLIB_INCLUDE_DIR:PATH=${ZLIB_INCLUDE_DIR} + -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} + -DDOWNLOAD_SOURCES:BOOL=ON + -DINSTALL_WINDOWS_TRADITIONAL:BOOL=OFF + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS} + DEPENDS zlib +) + +set(_python_DIR ${PYTHON_INSTALL_DIR}) + +if(UNIX) + set(_python_IMPORT_SUFFIX so) + if(APPLE) + set(_python_IMPORT_SUFFIX dylib) + endif() + set(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include/python2.7" CACHE PATH "Python include dir" FORCE) + set(PYTHON_LIBRARY "${PYTHON_INSTALL_DIR}/lib/libpython2.7.${_python_IMPORT_SUFFIX}" CACHE FILEPATH "Python library" FORCE) + set(PYTHON_EXECUTABLE ${PYTHON_INSTALL_DIR}/bin/python CACHE FILEPATH "Python executable" FORCE) + set(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/lib/python2.7/site-packages" CACHE PATH "Python site-packages path" FORCE) +elseif(WIN32) + set(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include" CACHE PATH "Python include dir" FORCE) + set(PYTHON_LIBRARY "${PYTHON_INSTALL_DIR}/libs/python27.lib" CACHE FILEPATH "Python library" FORCE) + set(PYTHON_EXECUTABLE "${PYTHON_INSTALL_DIR}/bin/python.exe" CACHE FILEPATH "Python executable" FORCE) + set(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/Lib/site-packages" CACHE PATH "Python site-packages path" FORCE) +else() + message(FATAL_ERROR "Unknown system !") +endif() diff --git a/cmake/external/swig.cmake b/cmake/external/swig.cmake new file mode 100644 index 0000000000..9dc112b98e --- /dev/null +++ b/cmake/external/swig.cmake @@ -0,0 +1,75 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Look for system swig +# FIND_PACKAGE(SWIG) + +#IF(NOT ${SWIG_FOUND}) + # build swig as an external project + INCLUDE(ExternalProject) + SET(SWIG_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/swig) + SET(SWIG_INSTALL_DIR ${PROJECT_BINARY_DIR}/swig) + SET(SWIG_TARGET_VERSION "3.0.2") + SET(SWIG_DOWNLOAD_SRC_MD5 "62f9b0d010cef36a13a010dc530d0d41") + SET(SWIG_DOWNLOAD_WIN_MD5 "3f18de4fc09ab9abb0d3be37c11fbc8f") + + IF(WIN32) + # swig.exe available as pre-built binary on Windows: + ExternalProject_Add(swig + URL http://prdownloads.sourceforge.net/swig/swigwin-${SWIG_TARGET_VERSION}.zip + URL_MD5 ${SWIG_DOWNLOAD_WIN_MD5} + SOURCE_DIR ${SWIG_SOURCES_DIR} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + ) + SET(SWIG_DIR ${SWIG_SOURCES_DIR} CACHE FILEPATH "SWIG Directory" FORCE) + SET(SWIG_EXECUTABLE ${SWIG_SOURCES_DIR}/swig.exe CACHE FILEPATH "SWIG Executable" FORCE) + + ELSE(WIN32) + # From PCRE configure + ExternalProject_Add(pcre + GIT_REPOSITORY https://github.com/svn2github/pcre.git + PREFIX ${SWIG_SOURCES_DIR}/pcre + UPDATE_COMMAND "" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SWIG_INSTALL_DIR}/pcre + ) + + # swig uses bison find it by cmake and pass it down + FIND_PACKAGE(BISON) + + # From SWIG configure + ExternalProject_Add(swig + URL https://github.com/swig/swig/archive/rel-3.0.10.tar.gz + PREFIX ${SWIG_SOURCES_DIR} + UPDATE_COMMAND "" + CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && ./autogen.sh + CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && + env "PCRE_LIBS=${SWIG_INSTALL_DIR}/pcre/lib/libpcre.a \ + ${SWIG_INSTALL_DIR}/pcre/lib/libpcrecpp.a \ + ${SWIG_INSTALL_DIR}/pcre/lib/libpcreposix.a" + ./configure + --prefix=${SWIG_INSTALL_DIR} + --with-pcre-prefix=${SWIG_INSTALL_DIR}/pcre + --with-python=${PYTHON_EXECUTABLE} + BUILD_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make + INSTALL_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make install + DEPENDS pcre python + ) + + set(SWIG_DIR ${SWIG_INSTALL_DIR}/share/swig/${SWIG_TARGET_VERSION} CACHE FILEPATH "SWIG Directory" FORCE) + set(SWIG_EXECUTABLE ${SWIG_INSTALL_DIR}/bin/swig CACHE FILEPATH "SWIG Executable" FORCE) + ENDIF(WIN32) +#ENDIF() + diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake new file mode 100644 index 0000000000..57864aca69 --- /dev/null +++ b/cmake/external/warpctc.cmake @@ -0,0 +1,39 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(ExternalProject) + +SET(WARPCTC_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/warpctc) +SET(WARPCTC_INSTALL_DIR ${PROJECT_BINARY_DIR}/warpctc) + +ExternalProject_Add( + warpctc + GIT_REPOSITORY "https://github.com/gangliao/warp-ctc.git" + PREFIX ${WARPCTC_SOURCES_DIR} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${WARPCTC_INSTALL_DIR} + CMAKE_ARGS -DWITH_GPU=${CUDA_FOUND} +) + +SET(WARPCTC_INCLUDE_DIR "${WARP_INSTALL_DIR}/include" CACHE PATH "Warp-ctc Directory" FORCE) +INCLUDE_DIRECTORIES(${WARPCTC_INCLUDE_DIR}) + +IF(WIN32) + set(WARPCTC_LIBRARIES + "${WARPCTC_INSTALL_DIR}/lib/warpctc.dll" CACHE FILEPATH "Warp-ctc Library" FORCE) +ELSE(WIN32) + set(WARPCTC_LIBRARIES + "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.so" CACHE FILEPATH "Warp-ctc Library" FORCE) +ENDIF(WIN32) + +LIST(APPEND external_project_dependencies warpctc) diff --git a/cmake/external/zlib.cmake b/cmake/external/zlib.cmake new file mode 100644 index 0000000000..ec44467aa7 --- /dev/null +++ b/cmake/external/zlib.cmake @@ -0,0 +1,44 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDE(ExternalProject) + +SET(ZLIB_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/zlib) +SET(ZLIB_INSTALL_DIR ${PROJECT_BINARY_DIR}/zlib) + +ExternalProject_Add( + zlib + GIT_REPOSITORY "https://github.com/madler/zlib.git" + GIT_TAG "v1.2.8" + PREFIX ${ZLIB_SOURCES_DIR} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${ZLIB_INSTALL_DIR} + CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF + CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON + CMAKE_ARGS -DCMAKE_MACOSX_RPATH=ON + LOG_DOWNLOAD =ON + UPDATE_COMMAND "" +) + +SET(ZLIB_ROOT ${ZLIB_INSTALL_DIR} CACHE PATH "zlib root directory." FORCE) + +SET(ZLIB_INCLUDE_DIR "${ZLIB_INSTALL_DIR}/include" CACHE PATH "zlib include directory." FORCE) +INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR}) + +IF(WIN32) + SET(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/zlibstatic.lib" CACHE FILEPATH "zlib library." FORCE) +ELSE(WIN32) + set(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/libz.a" CACHE FILEPATH "zlib library." FORCE) +ENDIF(WIN32) + +LIST(APPEND external_project_dependencies zlib) From 32b28c6429451b4ea7b91e002b139919491ea3a3 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Tue, 27 Dec 2016 11:50:06 +0800 Subject: [PATCH 051/119] add remote updater in api and swig --- paddle/api/Paddle.swig | 1 + paddle/api/PaddleAPI.h | 2 ++ paddle/api/ParameterUpdater.cpp | 18 +++++++++++++++--- paddle/trainer/RemoteParameterUpdater.h | 4 ++-- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/paddle/api/Paddle.swig b/paddle/api/Paddle.swig index 3365927f9b..068ba286c0 100644 --- a/paddle/api/Paddle.swig +++ b/paddle/api/Paddle.swig @@ -178,6 +178,7 @@ namespace std { %newobject ParameterOptimizer::create; %newobject ParameterOptimizer::needSpecialTraversal; %newobject ParameterUpdater::createLocalUpdater; +%newobject ParameterUpdater::createRemoteUpdater; %feature("director") UpdateCallback; %feature("autodoc", 1); // To generate method stub, for code hint in ide diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 0a273f9f6f..f70a8ce26b 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -797,6 +797,8 @@ private: public: static ParameterUpdater* createLocalUpdater(OptimizationConfig* config); + static ParameterUpdater* createRemoteUpdater(OptimizationConfig* config, + int passCount); ~ParameterUpdater(); /** diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 7cd8ed7e39..e84bb63866 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -15,15 +15,27 @@ limitations under the License. */ #include "PaddleAPI.h" #include "PaddleAPIPrivate.h" +#include "paddle/trainer/RemoteParameterUpdater.h" #include "paddle/trainer/ThreadParameterUpdater.h" ParameterUpdater::ParameterUpdater() : m(new ParameterUpdaterPrivate()) {} ParameterUpdater *ParameterUpdater::createLocalUpdater( OptimizationConfig *config) { - auto param = new ParameterUpdater(); - param->m->updater.reset(new paddle::SgdThreadUpdater(config->m->getConfig())); - return param; + auto updater = new ParameterUpdater(); + updater->m->updater.reset( + new paddle::SgdThreadUpdater(config->m->getConfig())); + return updater; +} + +ParameterUpdater *ParameterUpdater::createRemoteUpdater( + OptimizationConfig *config, int passCount) { + auto updater = new ParameterUpdater(); + std::unique_ptr localUpdater; + localUpdater.reset(new paddle::SgdThreadUpdater(config->m->getConfig())); + updater->m->updater.reset(new paddle::ConcurrentRemoteParameterUpdater( + config->m->getConfig(), passCount, std::move(localUpdater))); + return updater; } ParameterUpdater::~ParameterUpdater() { delete m; } diff --git a/paddle/trainer/RemoteParameterUpdater.h b/paddle/trainer/RemoteParameterUpdater.h index 7794b20900..5e82c94475 100644 --- a/paddle/trainer/RemoteParameterUpdater.h +++ b/paddle/trainer/RemoteParameterUpdater.h @@ -56,7 +56,7 @@ class RemoteParameterUpdater : public ParameterUpdater { public: RemoteParameterUpdater( const OptimizationConfig& config, - int expectedPpassCount, + int expectedPassCount, std::unique_ptr&& localUpdater = nullptr); ~RemoteParameterUpdater() { if (controllerThread_) { @@ -146,7 +146,7 @@ protected: BatchStatus batchStatus_; /// controller thread for sync-sgd std::unique_ptr controllerThread_; - /// passed alread finished + /// passed already finished int64_t passCount_; /// expected passes to finished int64_t expectedPassCount_; From eca45928d5f3f9b1c2219fd71adb72160fee9edf Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 27 Dec 2016 13:15:27 +0800 Subject: [PATCH 052/119] Fix merge errors. --- paddle/api/PaddleAPI.h | 2 +- paddle/utils/common.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index e83718448d..09c891871a 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -870,7 +870,7 @@ struct EvaluatorPrivate; class Evaluator { private: Evaluator(); - DISABLE_COPY_AND_ASSIGN(Evaluator); + DISABLE_COPY(Evaluator); public: ~Evaluator(); diff --git a/paddle/utils/common.h b/paddle/utils/common.h index 3ff0b86947..202a9d980d 100644 --- a/paddle/utils/common.h +++ b/paddle/utils/common.h @@ -14,8 +14,6 @@ limitations under the License. */ #pragma once -namespace paddle { - /** * Disable copy macro. */ @@ -24,6 +22,8 @@ namespace paddle { class_name(const class_name &other) = delete; \ class_name &operator=(const class_name &other) = delete +namespace paddle { + #ifdef PADDLE_TYPE_DOUBLE using real = double; #else From def00bc1064e5960568666f690773a8e5445f888 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Tue, 27 Dec 2016 16:44:31 +0800 Subject: [PATCH 053/119] Update index_cn.rst --- doc/faq/index_cn.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/faq/index_cn.rst b/doc/faq/index_cn.rst index ea0ef25f00..7d425a05d4 100644 --- a/doc/faq/index_cn.rst +++ b/doc/faq/index_cn.rst @@ -72,7 +72,7 @@ PaddlePaddle支持非常多的优化算法(Optimizer),不同的优化算法需 减少数据载入的耗时 ++++++++++++++++++ -使用 :code:`pydataprovider`时,可以减少缓存池的大小,同时设置内存缓存功能,即可以极大的加速数据载入流程。 +使用\ :code:`pydataprovider`\ 时,可以减少缓存池的大小,同时设置内存缓存功能,即可以极大的加速数据载入流程。 :code:`DataProvider` 缓存池的减小,和之前减小通过减小缓存池来减小内存占用的原理一致。 .. literalinclude:: src/reduce_min_pool_size.py From 6cd4b6e041c09a4ba12a5bfd236da2891364dcb4 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 17:11:26 +0800 Subject: [PATCH 054/119] Update external libs --- cmake/external/numpy.cmake | 104 ++++++++++++++++----------- cmake/external/openblas.cmake | 2 - cmake/external/python.cmake | 131 ++++++++++++++++++---------------- cmake/external/swig.cmake | 24 +++++-- cmake/external/warpctc.cmake | 11 ++- 5 files changed, 159 insertions(+), 113 deletions(-) diff --git a/cmake/external/numpy.cmake b/cmake/external/numpy.cmake index de3e6492cd..607ff31789 100644 --- a/cmake/external/numpy.cmake +++ b/cmake/external/numpy.cmake @@ -12,46 +12,64 @@ # See the License for the specific language governing permissions and # limitations under the License. -INCLUDE(ExternalProject) - -SET(NUMPY_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/numpy) -SET(NUMPY_INSTALL_DIR ${PROJECT_BINARY_DIR}/numpy) -set(NUMPY_VERSION "v1.11.3") - -# setuptools -ExternalProject_Add(setuptools - PREFIX ${PYTHON_SOURCES_DIR}/setuptools - URL http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz - URL_MD5 7df2a529a074f613b509fb44feefe74e - BUILD_IN_SOURCE 1 - UPDATE_COMMAND "" - PATCH_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python zlib -) - -ExternalProject_Add(cython - PREFIX ${PYTHON_SOURCES_DIR}/cython - GIT_REPOSITORY https://github.com/cython/cython.git - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - PATCH_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python -) - -ExternalProject_Add(numpy - GIT_REPOSITORY https://github.com/numpy/numpy.git - GIT_TAG ${NUMPY_VERSION} - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - PREFIX ${NUMPY_SOURCES_DIR} - BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py build - INSTALL_COMMAND ${PYTHON_EXECUTABLE} setup.py install - BUILD_IN_SOURCE 1 - DEPENDS python setuptools cython -) +FIND_PACKAGE(NumPy) + +IF(NOT ${NUMPY_FOUND}) + + INCLUDE(ExternalProject) + + SET(NUMPY_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/numpy) + SET(NUMPY_INSTALL_DIR ${PROJECT_BINARY_DIR}/numpy) + set(NUMPY_VERSION "v1.11.3") + + ExternalProject_Add(setuptools + PREFIX ${PYTHON_SOURCES_DIR}/setuptools + URL http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz + URL_MD5 7df2a529a074f613b509fb44feefe74e + BUILD_IN_SOURCE 1 + UPDATE_COMMAND "" + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python zlib + ) + + ExternalProject_Add(cython + PREFIX ${PYTHON_SOURCES_DIR}/cython + GIT_REPOSITORY https://github.com/cython/cython.git + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND "" + UPDATE_COMMAND "" + PATCH_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python + ) + + ExternalProject_Add(numpy + GIT_REPOSITORY https://github.com/numpy/numpy.git + GIT_TAG ${NUMPY_VERSION} + CONFIGURE_COMMAND "" + UPDATE_COMMAND "" + PREFIX ${NUMPY_SOURCES_DIR} + BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py build + INSTALL_COMMAND ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools cython + ) + + # find numpy include directory + FILE(WRITE ${PROJECT_BINARY_DIR}/FindNumpyPath.py + "try: import numpy; print(numpy.get_include())\nexcept:pass\n") + + EXEC_PROGRAM("${PYTHON_EXECUTABLE}" ${PROJECT_BINARY_DIR} + ARGS "FindNumpyPath.py" + OUTPUT_VARIABLE NUMPY_PATH) + + FIND_PATH(PYTHON_NUMPY_INCLUDE_DIR numpy/arrayobject.h + HINTS "${NUMPY_PATH}" "${PYTHON_INCLUDE_PATH}") + + INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) + +ENDIF() diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index d1220036a0..2683153b49 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# INCLUDE(cblas) - INCLUDE(ExternalProject) SET(CBLAS_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/openblas) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index b459913314..2354f555db 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -12,72 +12,81 @@ # See the License for the specific language governing permissions and # limitations under the License. -INCLUDE(ExternalProject) +FIND_PACKAGE(PythonLibs 2.7) +FIND_PACKAGE(PythonInterp 2.7) -SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/python) -SET(PYTHON_INSTALL_DIR ${PROJECT_BINARY_DIR}/python) +IF((NOT ${PYTHONINTERP_FOUND}) OR (NOT ${PYTHONLIBS_FOUND})) -if(MSVC) - list(APPEND EXTERNAL_PROJECT_OPTIONAL_ARGS - PATCH_COMMAND ${CMAKE_COMMAND} - -DPYTHON_SRC_DIR:PATH=${_python_SOURCE_DIR} - -P ${CMAKE_CURRENT_LIST_DIR}/PythonPatch.cmake - ) -endif() + INCLUDE(ExternalProject) -if(APPLE) - list(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS - -DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON - ) -endif() + SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/python) + SET(PYTHON_INSTALL_DIR ${PROJECT_BINARY_DIR}/python) -set(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) + IF(MSVC) + LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_ARGS + PATCH_COMMAND ${CMAKE_COMMAND} + -DPYTHON_SRC_DIR:PATH=${_python_SOURCE_DIR} + -P ${CMAKE_CURRENT_LIST_DIR}/PythonPatch.cmake + ) + ENDIF() -# Force Python build to "Release". -if(CMAKE_CONFIGURATION_TYPES) - set(SAVED_CMAKE_CFG_INTDIR ${CMAKE_CFG_INTDIR}) - set(CMAKE_CFG_INTDIR "Release") -else() - list(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS - -DCMAKE_BUILD_TYPE:STRING=Release) -endif() + IF(APPLE) + LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS + -DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON + ) + ENDIF() -ExternalProject_Add(python - GIT_REPOSITORY "https://github.com/python-cmake-buildsystem/python-cmake-buildsystem.git" - GIT_TAG "ed5f9bcee540e47f82fa17f8360b820591aa6d66" - PREFIX ${PYTHON_SOURCES_DIR} - UPDATE_COMMAND "" - CMAKE_CACHE_ARGS - -DCMAKE_INSTALL_PREFIX:PATH=${PYTHON_INSTALL_DIR} - -DBUILD_SHARED:BOOL=OFF - -DBUILD_STATIC:BOOL=ON - -DUSE_SYSTEM_LIBRARIES:BOOL=OFF - -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} - -DZLIB_INCLUDE_DIR:PATH=${ZLIB_INCLUDE_DIR} - -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} - -DDOWNLOAD_SOURCES:BOOL=ON - -DINSTALL_WINDOWS_TRADITIONAL:BOOL=OFF - ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} - ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS} - DEPENDS zlib -) + SET(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) -set(_python_DIR ${PYTHON_INSTALL_DIR}) + # Force Python build to "Release". + IF(CMAKE_CONFIGURATION_TYPES) + SET(SAVED_CMAKE_CFG_INTDIR ${CMAKE_CFG_INTDIR}) + SET(CMAKE_CFG_INTDIR "Release") + ELSE() + LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS + -DCMAKE_BUILD_TYPE:STRING=Release) + ENDIF() -if(UNIX) - set(_python_IMPORT_SUFFIX so) - if(APPLE) - set(_python_IMPORT_SUFFIX dylib) - endif() - set(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include/python2.7" CACHE PATH "Python include dir" FORCE) - set(PYTHON_LIBRARY "${PYTHON_INSTALL_DIR}/lib/libpython2.7.${_python_IMPORT_SUFFIX}" CACHE FILEPATH "Python library" FORCE) - set(PYTHON_EXECUTABLE ${PYTHON_INSTALL_DIR}/bin/python CACHE FILEPATH "Python executable" FORCE) - set(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/lib/python2.7/site-packages" CACHE PATH "Python site-packages path" FORCE) -elseif(WIN32) - set(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include" CACHE PATH "Python include dir" FORCE) - set(PYTHON_LIBRARY "${PYTHON_INSTALL_DIR}/libs/python27.lib" CACHE FILEPATH "Python library" FORCE) - set(PYTHON_EXECUTABLE "${PYTHON_INSTALL_DIR}/bin/python.exe" CACHE FILEPATH "Python executable" FORCE) - set(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/Lib/site-packages" CACHE PATH "Python site-packages path" FORCE) -else() - message(FATAL_ERROR "Unknown system !") -endif() + ExternalProject_Add(python + GIT_REPOSITORY "https://github.com/python-cmake-buildsystem/python-cmake-buildsystem.git" + GIT_TAG "ed5f9bcee540e47f82fa17f8360b820591aa6d66" + PREFIX ${PYTHON_SOURCES_DIR} + UPDATE_COMMAND "" + CMAKE_CACHE_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${PYTHON_INSTALL_DIR} + -DBUILD_SHARED:BOOL=OFF + -DBUILD_STATIC:BOOL=ON + -DUSE_SYSTEM_LIBRARIES:BOOL=OFF + -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} + -DZLIB_INCLUDE_DIR:PATH=${ZLIB_INCLUDE_DIR} + -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} + -DDOWNLOAD_SOURCES:BOOL=ON + -DINSTALL_WINDOWS_TRADITIONAL:BOOL=OFF + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS} + DEPENDS zlib + ) + + SET(_python_DIR ${PYTHON_INSTALL_DIR}) + + IF(UNIX) + SET(_python_IMPORT_SUFFIX a) + IF(APPLE) + SET(_python_IMPORT_SUFFIX lib) + ENDIF() + SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include/python2.7" CACHE PATH "Python include dir" FORCE) + SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/lib/libpython2.7.${_python_IMPORT_SUFFIX}" CACHE FILEPATH "Python library" FORCE) + SET(PYTHON_EXECUTABLE ${PYTHON_INSTALL_DIR}/bin/python CACHE FILEPATH "Python executable" FORCE) + SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/lib/python2.7/site-packages" CACHE PATH "Python site-packages path" FORCE) + ELSEIF(WIN32) + SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include" CACHE PATH "Python include dir" FORCE) + SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/libs/python27.lib" CACHE FILEPATH "Python library" FORCE) + SET(PYTHON_EXECUTABLE "${PYTHON_INSTALL_DIR}/bin/python.exe" CACHE FILEPATH "Python executable" FORCE) + SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/Lib/site-packages" CACHE PATH "Python site-packages path" FORCE) + ELSE() + MESSAGE(FATAL_ERROR "Unknown system !") + ENDIF() + +INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) + +ENDIF() diff --git a/cmake/external/swig.cmake b/cmake/external/swig.cmake index 9dc112b98e..1ec61660bc 100644 --- a/cmake/external/swig.cmake +++ b/cmake/external/swig.cmake @@ -13,9 +13,9 @@ # limitations under the License. # Look for system swig -# FIND_PACKAGE(SWIG) +FIND_PACKAGE(SWIG) -#IF(NOT ${SWIG_FOUND}) +IF(NOT ${SWIG_FOUND}) # build swig as an external project INCLUDE(ExternalProject) SET(SWIG_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/swig) @@ -62,14 +62,28 @@ ./configure --prefix=${SWIG_INSTALL_DIR} --with-pcre-prefix=${SWIG_INSTALL_DIR}/pcre - --with-python=${PYTHON_EXECUTABLE} BUILD_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make INSTALL_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make install - DEPENDS pcre python + DEPENDS pcre ) set(SWIG_DIR ${SWIG_INSTALL_DIR}/share/swig/${SWIG_TARGET_VERSION} CACHE FILEPATH "SWIG Directory" FORCE) set(SWIG_EXECUTABLE ${SWIG_INSTALL_DIR}/bin/swig CACHE FILEPATH "SWIG Executable" FORCE) ENDIF(WIN32) -#ENDIF() +ENDIF() +FUNCTION(generate_python_api target_name) + ADD_CUSTOM_COMMAND(OUTPUT ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py + ${PROJ_ROOT}/paddle/Paddle_wrap.cxx + ${PROJ_ROOT}/paddle/Paddle_wrap.h + COMMAND ${SWIG_EXECUTABLE} -python -c++ -outcurrentdir -I../ api/Paddle.swig + && mv ${PROJ_ROOT}/paddle/swig_paddle.py ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py + DEPENDS ${PROJ_ROOT}/paddle/api/Paddle.swig + ${PROJ_ROOT}/paddle/api/PaddleAPI.h + WORKING_DIRECTORY ${PROJ_ROOT}/paddle + COMMENT "Generate Python API from swig") + ADD_CUSTOM_TARGET(${target_name} ALL DEPENDS + ${PROJ_ROOT}/paddle/Paddle_wrap.cxx + ${PROJ_ROOT}/paddle/Paddle_wrap.h + ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py) +ENDFUNCTION(generate_python_api) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 57864aca69..4fdd47acdb 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -17,22 +17,29 @@ INCLUDE(ExternalProject) SET(WARPCTC_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/warpctc) SET(WARPCTC_INSTALL_DIR ${PROJECT_BINARY_DIR}/warpctc) +IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + SET(USE_OMP ON) +ELSE() + SET(USE_OMP OFF) +ENDIF() + ExternalProject_Add( warpctc GIT_REPOSITORY "https://github.com/gangliao/warp-ctc.git" PREFIX ${WARPCTC_SOURCES_DIR} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${WARPCTC_INSTALL_DIR} CMAKE_ARGS -DWITH_GPU=${CUDA_FOUND} + CMAKE_ARGS -DWITH_OMP=${USE_OMP} ) SET(WARPCTC_INCLUDE_DIR "${WARP_INSTALL_DIR}/include" CACHE PATH "Warp-ctc Directory" FORCE) INCLUDE_DIRECTORIES(${WARPCTC_INCLUDE_DIR}) IF(WIN32) - set(WARPCTC_LIBRARIES + SET(WARPCTC_LIBRARIES "${WARPCTC_INSTALL_DIR}/lib/warpctc.dll" CACHE FILEPATH "Warp-ctc Library" FORCE) ELSE(WIN32) - set(WARPCTC_LIBRARIES + SET(WARPCTC_LIBRARIES "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.so" CACHE FILEPATH "Warp-ctc Library" FORCE) ENDIF(WIN32) From 662f174b856353105b5291be8f3a855ed54f5f6d Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 17:12:39 +0800 Subject: [PATCH 055/119] Refine cmake file names --- .gitignore | 1 + cmake/{FindPythonModule.cmake => python_module.cmake} | 0 cmake/{FindAVX.cmake => simd.cmake} | 0 3 files changed, 1 insertion(+) rename cmake/{FindPythonModule.cmake => python_module.cmake} (100%) rename cmake/{FindAVX.cmake => simd.cmake} (100%) diff --git a/.gitignore b/.gitignore index 1c9730a5ad..0a15b996e2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ build/ .pydevproject Makefile .test_env/ +third_party/ *~ bazel-* diff --git a/cmake/FindPythonModule.cmake b/cmake/python_module.cmake similarity index 100% rename from cmake/FindPythonModule.cmake rename to cmake/python_module.cmake diff --git a/cmake/FindAVX.cmake b/cmake/simd.cmake similarity index 100% rename from cmake/FindAVX.cmake rename to cmake/simd.cmake From 62b55cc6ab9c21c9a09b3ad0c9fd94e5e8a3bfd6 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 17:13:21 +0800 Subject: [PATCH 056/119] Remove paddle internals --- python/CMakeLists.txt | 5 ----- python/setup.py.in | 5 ----- 2 files changed, 10 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index dce0b90952..6b80e4d58e 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -10,11 +10,6 @@ set(PY_FILES paddle/__init__.py ${HELPERS_PY_FILES} ${UTILS_PY_FILES}) -set(PADDLE_INTERNAL_PACKAGE "") -if (PADDLE_WITH_INTERNAL) - set(PADDLE_INTERNAL_PACKAGE "paddle.internals") -endif() - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) diff --git a/python/setup.py.in b/python/setup.py.in index d2fb95f27f..b66a42e87c 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -1,16 +1,11 @@ from setuptools import setup -INTERNAL_PACKAGE='${PADDLE_INTERNAL_PACKAGE}' - packages=['paddle', 'paddle.proto', 'paddle.trainer', 'paddle.trainer_config_helpers', 'paddle.utils'] -if len(INTERNAL_PACKAGE) != 0: - packages.append(INTERNAL_PACKAGE) - setup(name='paddle', version='${PADDLE_VERSION}', description='Parallel Distributed Deep Learning', From a02ec8c9323558d035fc2ca78d5076decca52c6f Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 17:13:39 +0800 Subject: [PATCH 057/119] Refine CMakeLists --- CMakeLists.txt | 180 ++++++++++--------------------------- cmake/check_packages.cmake | 31 +------ cmake/definitions.cmake | 62 +++++++++++++ cmake/rdma.cmake | 132 ++++++++++++++------------- cmake/swig.cmake | 15 ---- cmake/util.cmake | 34 +------ cmake/version.cmake | 1 + 7 files changed, 184 insertions(+), 271 deletions(-) create mode 100644 cmake/definitions.cmake delete mode 100644 cmake/swig.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 65fbbb481c..7db4c9f1d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,163 +4,75 @@ project(paddle CXX C) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(PROJ_ROOT ${CMAKE_SOURCE_DIR}) -include(package) -find_package(SWIG 2.0) -find_package(CUDA QUIET) -find_package(Protobuf REQUIRED) - -# Check protobuf library version. -execute_process(COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --version - OUTPUT_VARIABLE PROTOBUF_VERSION) -string(REPLACE "libprotoc " "" PROTOBUF_VERSION ${PROTOBUF_VERSION}) -set(PROTOBUF_3 OFF) -if (${PROTOBUF_VERSION} VERSION_GREATER "3.0.0" OR ${PROTOBUF_VERSION} VERSION_EQUAL "3.0.0") - set(PROTOBUF_3 ON) -endif() - -find_package(PythonLibs 2.7 REQUIRED) -find_package(PythonInterp 2.7 REQUIRED) -find_package(ZLIB REQUIRED) -find_package(NumPy REQUIRED) -find_package(Threads REQUIRED) -find_package(AVX QUIET) -find_package(Glog REQUIRED) -find_package(Gflags REQUIRED) -find_package(GTest) find_package(Sphinx) find_package(Doxygen) -include(cblas) -find_program(M4_EXECUTABLE m4) -###################### Configurations ########################### +find_package(CUDA QUIET) +find_package(Git REQUIRED) +find_package(Threads REQUIRED) + +include(simd) + +###################### Configurations ############################ option(WITH_DSO "Compile PaddlePaddle with dynamic linked libraries" ON) option(WITH_GPU "Compile PaddlePaddle with gpu" ${CUDA_FOUND}) option(WITH_DOUBLE "Compile PaddlePaddle with double precision, otherwise use single precision" OFF) option(WITH_AVX "Compile PaddlePaddle with avx intrinsics" ${AVX_FOUND}) option(WITH_PYTHON "Compile PaddlePaddle with python interpreter" ON) -option(WITH_STYLE_CHECK "Style Check for PaddlePaddle" ${PYTHONINTERP_FOUND}) +option(WITH_STYLE_CHECK "Style Check for PaddlePaddle" ON) option(WITH_RDMA "Compile PaddlePaddle with rdma support" OFF) option(WITH_TIMER "Compile PaddlePaddle use timer" OFF) option(WITH_PROFILER "Compile PaddlePaddle use gpu profiler" OFF) -option(WITH_TESTING "Compile and run unittest for PaddlePaddle" ${GTEST_FOUND}) +option(WITH_TESTING "Compile and run unittest for PaddlePaddle" ON) option(WITH_DOC "Compile PaddlePaddle with documentation" OFF) -option(WITH_SWIG_PY "Compile PaddlePaddle with py PaddlePaddle prediction api" ${SWIG_FOUND}) +option(WITH_SWIG_PY "Compile PaddlePaddle with py PaddlePaddle prediction api" ON) option(ON_TRAVIS "Running test on travis-ci or not." OFF) option(ON_COVERALLS "Generating code coverage data on coveralls or not." OFF) option(COVERALLS_UPLOAD "Uploading the generated coveralls json." ON) +include(external/zlib) # download, build, install zlib +include(external/gflags) # download, build, install gflags +include(external/glog) # download, build, install glog +include(external/gtest) # download, build, install gtest +include(external/protobuf) # download, build, install protobuf +include(external/openblas) # download, build, install openblas +include(external/python) # download, build, install python +include(external/numpy) # download, build, install numpy +include(external/swig) # download, build, install swig +include(external/warpctc) # download, build, install warpctc + +include(package) # set paddle packages +include(cpplint) # set paddle c++ style +include(ccache) # set ccache for compilation +include(util) # set unittest and link libs +include(rdma) # set rdma libraries +include(flags) # set paddle compile flags +include(cudnn) # set cudnn libraries +include(version) # set PADDLE_VERSION +include(coveralls) # set code coverage +include(python_module) # set python module + +include(check_packages) # check configuration +include(definitions) # add paddle definitions -include(cpplint) -include(ccache) -if(WITH_RDMA) - include(rdma) -endif() -include(util) -include(flags) -include(cudnn) -include(FindPythonModule) -include(check_packages) -include(swig) -include(coveralls) - -# Set PaddlePaddle version to Git tag name or Git commit ID. -find_package(Git REQUIRED) -# version.cmake will get the current PADDLE_VERSION -include(version) -add_definitions(-DPADDLE_VERSION=${PADDLE_VERSION}) - -if(NOT WITH_GPU) - add_definitions(-DPADDLE_ONLY_CPU) - add_definitions(-DHPPL_STUB_FUNC) - - list(APPEND CMAKE_CXX_SOURCE_FILE_EXTENSIONS cu) -else() - if(${CUDA_VERSION_MAJOR} VERSION_LESS 7) - message(FATAL_ERROR "Paddle need CUDA >= 7.0 to compile") - endif() - - if(NOT CUDNN_FOUND) - message(FATAL_ERROR "Paddle need cudnn to compile") - endif() - - if(WITH_AVX) - set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} "-Xcompiler ${AVX_FLAG}") - else(WITH_AVX) - set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} "-Xcompiler ${SSE3_FLAG}") - endif(WITH_AVX) - - # Include cuda and cudnn - include_directories(${CUDNN_INCLUDE_DIR}) - include_directories(${CUDA_TOOLKIT_INCLUDE}) -endif(NOT WITH_GPU) - -if(WITH_DSO) - add_definitions(-DPADDLE_USE_DSO) -endif(WITH_DSO) - -if(WITH_DOUBLE) - add_definitions(-DPADDLE_TYPE_DOUBLE) - set(ACCURACY double) -else(WITH_DOUBLE) - set(ACCURACY float) -endif(WITH_DOUBLE) - -if(NOT WITH_TIMER) - add_definitions(-DPADDLE_DISABLE_TIMER) -endif(NOT WITH_TIMER) - -if(NOT WITH_PROFILER) - add_definitions(-DPADDLE_DISABLE_PROFILER) -endif(NOT WITH_PROFILER) - -if(WITH_AVX) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${AVX_FLAG}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${AVX_FLAG}") -else(WITH_AVX) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SSE3_FLAG}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SSE3_FLAG}") -endif(WITH_AVX) - -if(WITH_PYTHON) - include_directories(${PYTHON_INCLUDE_DIR}) - include_directories(${PYTHON_NUMPY_INCLUDE_DIR}) -else(WITH_PYTHON) - add_definitions(-DPADDLE_NO_PYTHON) -endif(WITH_PYTHON) - -if(WITH_RDMA) - include_directories("${RDMA_INC_DIR}") -else(WITH_RDMA) - add_definitions(-DPADDLE_DISABLE_RDMA) -endif(WITH_RDMA) - -# glog -include_directories(${LIBGLOG_INCLUDE_DIR}) - -#gflags -add_definitions(-DGFLAGS_NS=${GFLAGS_NAMESPACE}) -include_directories(${GFLAGS_INCLUDE_DIRS}) - -if(WITH_TESTING) - enable_testing() - include_directories(${GTEST_INCLUDE_DIRS}) -endif() - -include_directories("${CBLAS_INC_DIR}") include_directories("${PROJ_ROOT}") include_directories("${PROJ_ROOT}/paddle/cuda/include") -include_directories(${PROTOBUF_INCLUDE_DIRS}) include_directories("${CMAKE_CURRENT_BINARY_DIR}/proto") -if(EXISTS "${PROJ_ROOT}/paddle/internals/CMakeLists.txt") - set(PADDLE_WITH_INTERNAL ON) - include(paddle/internals/CMakeLists.txt) -else() - set(PADDLE_WITH_INTERNAL OFF) - set(INTERNAL_PROTO_PATH "") -endif() + +set(EXTERNAL_LIBS + # have not include gtest here. + ${GFLAGS_LIBRARIES} + ${GLOG_LIBRARIES} + ${CBLAS_LIBRARIES} + ${PROTOBUF_LIBRARIES} + ${WARPCTC_LIBRARIES} + ${ZLIB_LIBRARIES} +) + add_subdirectory(proto) add_subdirectory(paddle) add_subdirectory(python) + if(WITH_DOC) add_subdirectory(doc) endif() diff --git a/cmake/check_packages.cmake b/cmake/check_packages.cmake index afb84c6ff5..8f0ed26256 100644 --- a/cmake/check_packages.cmake +++ b/cmake/check_packages.cmake @@ -2,38 +2,13 @@ if(WITH_GPU) find_package(CUDA REQUIRED) # CUDA is required when use gpu -endif() - -if(WITH_PYTHON) - find_package(PythonLibs 2.6 REQUIRED) - find_package(PythonInterp REQUIRED) - find_package(NumPy REQUIRED) -endif() - -if(WITH_STYLE_CHECK) - find_package(PythonInterp REQUIRED) -endif() - -find_package(Glog REQUIRED) - -find_package(Gflags REQUIRED) - -if(WITH_TESTING) - find_package(GTest REQUIRED) -endif() +endif(WITH_GPU) if(WITH_DOC) find_package(Sphinx REQUIRED) find_python_module(recommonmark REQUIRED) -endif() +endif(WITH_DOC) if(WITH_SWIG_PY) - if(NOT SWIG_FOUND) - message(FATAL_ERROR "SWIG is not found. Please install swig or disable WITH_SWIG_PY") - endif() find_python_module(wheel REQUIRED) # package wheel -endif() - -if(NOT M4_EXECUTABLE) - message(FATAL_ERROR "Paddle need m4 to generate proto file.") -endif() +endif(WITH_SWIG_PY) diff --git a/cmake/definitions.cmake b/cmake/definitions.cmake new file mode 100644 index 0000000000..99a52ad764 --- /dev/null +++ b/cmake/definitions.cmake @@ -0,0 +1,62 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if(WITH_DSO) + add_definitions(-DPADDLE_USE_DSO) +endif(WITH_DSO) + +if(WITH_DOUBLE) + add_definitions(-DPADDLE_TYPE_DOUBLE) +endif(WITH_DOUBLE) + +if(NOT WITH_TIMER) + add_definitions(-DPADDLE_DISABLE_TIMER) +endif(NOT WITH_TIMER) + +if(NOT WITH_PROFILER) + add_definitions(-DPADDLE_DISABLE_PROFILER) +endif(NOT WITH_PROFILER) + +if(NOT WITH_GPU) + add_definitions(-DPADDLE_ONLY_CPU) + add_definitions(-DHPPL_STUB_FUNC) + + list(APPEND CMAKE_CXX_SOURCE_FILE_EXTENSIONS cu) +else() + if(${CUDA_VERSION_MAJOR} VERSION_LESS 7) + message(FATAL_ERROR "Paddle need CUDA >= 7.0 to compile") + endif() + + if(NOT CUDNN_FOUND) + message(FATAL_ERROR "Paddle need cudnn to compile") + endif() + + if(WITH_AVX) + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} "-Xcompiler ${AVX_FLAG}") + else(WITH_AVX) + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} "-Xcompiler ${SSE3_FLAG}") + endif(WITH_AVX) + + # Include cuda and cudnn + include_directories(${CUDNN_INCLUDE_DIR}) + include_directories(${CUDA_TOOLKIT_INCLUDE}) +endif(NOT WITH_GPU) + +if(WITH_AVX) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${AVX_FLAG}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${AVX_FLAG}") +else(WITH_AVX) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SSE3_FLAG}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SSE3_FLAG}") +endif(WITH_AVX) diff --git a/cmake/rdma.cmake b/cmake/rdma.cmake index e9a4da79aa..9ff1a77cac 100644 --- a/cmake/rdma.cmake +++ b/cmake/rdma.cmake @@ -5,72 +5,76 @@ # svn co https://svn.baidu.com/sys/ip/trunk/rdma/thirdparty rdma/ # we use static output in svn repositories to avoid implict bugs from not standard runtime env. -set(RDMA_ROOT $ENV{RDMA_ROOT} CACHE PATH "Folder contains RDMA sock library and thirdparty library") +if(WITH_RDMA) + set(RDMA_ROOT $ENV{RDMA_ROOT} CACHE PATH "Folder contains RDMA sock library and thirdparty library") -function(generate_rdma_links) - #redirect to current DIR to isolate the pollution from system runtime environment - #it can benifits unified control for different gcc environment. - #e.g, by default gcc48 did not refer /usr/lib64 which could contain low version - #runtime libraries that will crash process while loading it. That redirect trick - #can fix it. - execute_process( - COMMAND mkdir -p librdma - COMMAND ln -s -f /usr/lib64/libibverbs.so.1.0.0 librdma/libibverbs.so.1 - COMMAND ln -s -f /usr/lib64/libibverbs.so.1.0.0 librdma/libibverbs.so - COMMAND ln -s -f /usr/lib64/librdmacm.so.1.0.0 librdma/librdmacm.so.1 - COMMAND ln -s -f /usr/lib64/librdmacm.so.1.0.0 librdma/librdmacm.so - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) -endfunction(generate_rdma_links) - - -#check and set headers -find_path(RDMA_INC_SXISOCK sxi_sock.h PATHS ${RDMA_ROOT}/sockrdmav1/output/include) -find_path(RDMA_INC_XIO libxio.h PATHS ${RDMA_ROOT}/thirdparty/output/accelio) -find_path(RDMA_INC_EVENT event2 PATHS ${RDMA_ROOT}/thirdparty/output/libevent) -find_path(RDMA_INC_NUMA numa.h PATHS ${RDMA_ROOT}/thirdparty/output/libnuma) - -#check and set libs -find_library(RDMA_LIB_SXISOCK NAMES sxisock PATHS ${RDMA_ROOT}/sockrdmav1/output) -find_library(RDMA_LIB_XIO NAMES xio PATHS ${RDMA_ROOT}/thirdparty/output/accelio) -find_library(RDMA_LIB_EVENT NAMES event PATHS ${RDMA_ROOT}/thirdparty/output/libevent) -find_library(RDMA_LIB_EVENT_CORE NAMES event_core PATHS ${RDMA_ROOT}/thirdparty/output/libevent) -find_library(RDMA_LIB_EVENT_EXTRA NAMES event_extra PATHS ${RDMA_ROOT}/thirdparty/output/libevent) -find_library(RDMA_LIB_EVENT_PTHREADS NAMES event_pthreads PATHS ${RDMA_ROOT}/thirdparty/output/libevent) -find_library(RDMA_LIB_NUMA NAMES numa PATHS ${RDMA_ROOT}/thirdparty/output/libnuma) - -if( - RDMA_INC_SXISOCK AND - RDMA_INC_XIO AND - RDMA_INC_EVENT AND - RDMA_INC_NUMA AND - RDMA_LIB_SXISOCK AND - RDMA_LIB_XIO AND - RDMA_LIB_EVENT AND - RDMA_LIB_EVENT_CORE AND - RDMA_LIB_EVENT_EXTRA AND - RDMA_LIB_EVENT_PTHREADS AND - RDMA_LIB_NUMA + function(generate_rdma_links) + #redirect to current DIR to isolate the pollution from system runtime environment + #it can benifits unified control for different gcc environment. + #e.g, by default gcc48 did not refer /usr/lib64 which could contain low version + #runtime libraries that will crash process while loading it. That redirect trick + #can fix it. + execute_process( + COMMAND mkdir -p librdma + COMMAND ln -s -f /usr/lib64/libibverbs.so.1.0.0 librdma/libibverbs.so.1 + COMMAND ln -s -f /usr/lib64/libibverbs.so.1.0.0 librdma/libibverbs.so + COMMAND ln -s -f /usr/lib64/librdmacm.so.1.0.0 librdma/librdmacm.so.1 + COMMAND ln -s -f /usr/lib64/librdmacm.so.1.0.0 librdma/librdmacm.so + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + endfunction(generate_rdma_links) - set(RDMA_INC_DIR - ${RDMA_INC_SXISOCK} - ${RDMA_INC_XIO} - ${RDMA_INC_EVENT} - ${RDMA_INC_NUMA}) - set(RDMA_LIBS - ${RDMA_LIB_SXISOCK} - ${RDMA_LIB_XIO} - ${RDMA_LIB_EVENT} - ${RDMA_LIB_EVENT_CORE} - ${RDMA_LIB_EVENT_EXTRA} - ${RDMA_LIB_EVENT_PTHREADS} - ${RDMA_LIB_NUMA} - ) - set(RDMA_LD_FLAGS "-L./librdma -libverbs -lrdmacm -Xlinker -rpath ./librdma") - return() -endif() + #check and set headers + find_path(RDMA_INC_SXISOCK sxi_sock.h PATHS ${RDMA_ROOT}/sockrdmav1/output/include) + find_path(RDMA_INC_XIO libxio.h PATHS ${RDMA_ROOT}/thirdparty/output/accelio) + find_path(RDMA_INC_EVENT event2 PATHS ${RDMA_ROOT}/thirdparty/output/libevent) + find_path(RDMA_INC_NUMA numa.h PATHS ${RDMA_ROOT}/thirdparty/output/libnuma) + + #check and set libs + find_library(RDMA_LIB_SXISOCK NAMES sxisock PATHS ${RDMA_ROOT}/sockrdmav1/output) + find_library(RDMA_LIB_XIO NAMES xio PATHS ${RDMA_ROOT}/thirdparty/output/accelio) + find_library(RDMA_LIB_EVENT NAMES event PATHS ${RDMA_ROOT}/thirdparty/output/libevent) + find_library(RDMA_LIB_EVENT_CORE NAMES event_core PATHS ${RDMA_ROOT}/thirdparty/output/libevent) + find_library(RDMA_LIB_EVENT_EXTRA NAMES event_extra PATHS ${RDMA_ROOT}/thirdparty/output/libevent) + find_library(RDMA_LIB_EVENT_PTHREADS NAMES event_pthreads PATHS ${RDMA_ROOT}/thirdparty/output/libevent) + find_library(RDMA_LIB_NUMA NAMES numa PATHS ${RDMA_ROOT}/thirdparty/output/libnuma) -#if this module is not called, RDMA_INC_DIR RDMA_LIBS will be null, so top module always refer this variable + if( + RDMA_INC_SXISOCK AND + RDMA_INC_XIO AND + RDMA_INC_EVENT AND + RDMA_INC_NUMA AND + RDMA_LIB_SXISOCK AND + RDMA_LIB_XIO AND + RDMA_LIB_EVENT AND + RDMA_LIB_EVENT_CORE AND + RDMA_LIB_EVENT_EXTRA AND + RDMA_LIB_EVENT_PTHREADS AND + RDMA_LIB_NUMA + ) -message(FATAL_ERROR, "RDMA libraries are not found, try to set RDMA_ROOT or check all related libraries.") + set(RDMA_INC_DIR + ${RDMA_INC_SXISOCK} + ${RDMA_INC_XIO} + ${RDMA_INC_EVENT} + ${RDMA_INC_NUMA}) + set(RDMA_LIBS + ${RDMA_LIB_SXISOCK} + ${RDMA_LIB_XIO} + ${RDMA_LIB_EVENT} + ${RDMA_LIB_EVENT_CORE} + ${RDMA_LIB_EVENT_EXTRA} + ${RDMA_LIB_EVENT_PTHREADS} + ${RDMA_LIB_NUMA} + ) + set(RDMA_LD_FLAGS "-L./librdma -libverbs -lrdmacm -Xlinker -rpath ./librdma") + include_directories("${RDMA_INC_DIR}") + else() + #if this module is not called, RDMA_INC_DIR RDMA_LIBS will be null, so top module always refer this variable + message(FATAL_ERROR, "RDMA libraries are not found, try to set RDMA_ROOT or check all related libraries.") + endif() +else(WITH_RDMA) + set(RDMA_LIBS "") + set(RDMA_LD_FLAGS "") + add_definitions(-DPADDLE_DISABLE_RDMA) +endif(WITH_RDMA) diff --git a/cmake/swig.cmake b/cmake/swig.cmake deleted file mode 100644 index 97e87aa947..0000000000 --- a/cmake/swig.cmake +++ /dev/null @@ -1,15 +0,0 @@ -function(generate_python_api target_name) - add_custom_command(OUTPUT ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py - ${PROJ_ROOT}/paddle/Paddle_wrap.cxx - ${PROJ_ROOT}/paddle/Paddle_wrap.h - COMMAND swig -python -c++ -outcurrentdir -I../ api/Paddle.swig - && mv ${PROJ_ROOT}/paddle/swig_paddle.py ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py - DEPENDS ${PROJ_ROOT}/paddle/api/Paddle.swig - ${PROJ_ROOT}/paddle/api/PaddleAPI.h - WORKING_DIRECTORY ${PROJ_ROOT}/paddle - COMMENT "Generate Python API from swig") - add_custom_target(${target_name} ALL DEPENDS - ${PROJ_ROOT}/paddle/Paddle_wrap.cxx - ${PROJ_ROOT}/paddle/Paddle_wrap.h - ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py) -endfunction(generate_python_api) diff --git a/cmake/util.cmake b/cmake/util.cmake index 8a71b23c62..1b1e630661 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -81,18 +81,6 @@ function(link_paddle_exe TARGET_NAME) set(METRIC_LIBS "") endif() - if(PADDLE_WITH_INTERNAL) - set(INTERAL_LIBS paddle_internal_gserver paddle_internal_parameter) - target_circle_link_libraries(${TARGET_NAME} - ARCHIVE_START - paddle_internal_gserver - paddle_internal_owlqn - ARCHIVE_END - paddle_internal_parameter) - else() - set(INTERAL_LIBS "") - endif() - target_circle_link_libraries(${TARGET_NAME} ARCHIVE_START paddle_gserver @@ -109,20 +97,11 @@ function(link_paddle_exe TARGET_NAME) paddle_cuda paddle_test_main ${METRIC_LIBS} - ${PROTOBUF_LIBRARY} - ${LIBGLOG_LIBRARY} - ${GFLAGS_LIBRARIES} + ${EXTERNAL_LIBS} ${CMAKE_THREAD_LIBS_INIT} - ${CBLAS_LIBS} - ${ZLIB_LIBRARIES} - ${INTERAL_LIBS} - ${CMAKE_DL_LIBS}) - - if(WITH_RDMA) - target_link_libraries(${TARGET_NAME} - ${RDMA_LD_FLAGS} - ${RDMA_LIBS}) - endif() + ${CMAKE_DL_LIBS} + ${RDMA_LD_FLAGS} + ${RDMA_LIBS}) if(WITH_PYTHON) target_link_libraries(${TARGET_NAME} @@ -142,11 +121,6 @@ function(link_paddle_exe TARGET_NAME) target_link_libraries(${TARGET_NAME} rt) endif() endif() - - if(NOT WITH_DSO) - target_link_libraries(${TARGET_NAME} - ${WARPCTC_LIBRARY}) - endif() endfunction() # link_paddle_test diff --git a/cmake/version.cmake b/cmake/version.cmake index a0518e07e8..ac1583a24c 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -21,4 +21,5 @@ while ("${PADDLE_VERSION}" STREQUAL "") endif() endwhile() +add_definitions(-DPADDLE_VERSION=${PADDLE_VERSION}) message(STATUS "Paddle version is ${PADDLE_VERSION}") From 46cadaeaa98668ba868f7f9eef406a59b0176f46 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 23:23:25 +0800 Subject: [PATCH 058/119] Update external dependencies cmake --- cmake/external/numpy.cmake | 5 +++-- cmake/external/protobuf.cmake | 2 +- cmake/external/python.cmake | 8 ++++---- cmake/external/warpctc.cmake | 7 ++++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cmake/external/numpy.cmake b/cmake/external/numpy.cmake index 607ff31789..9d686ecaac 100644 --- a/cmake/external/numpy.cmake +++ b/cmake/external/numpy.cmake @@ -70,6 +70,7 @@ IF(NOT ${NUMPY_FOUND}) FIND_PATH(PYTHON_NUMPY_INCLUDE_DIR numpy/arrayobject.h HINTS "${NUMPY_PATH}" "${PYTHON_INCLUDE_PATH}") - INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) - ENDIF() + +INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) + diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 8acc6325b9..0138f082d9 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -22,7 +22,7 @@ ExternalProject_Add( PREFIX ${PROTOBUF_SOURCES_DIR} DEPENDS zlib GIT_REPOSITORY "https://github.com/google/protobuf.git" - GIT_TAG "v3.0.0" +# GIT_TAG "v3.1.0" CONFIGURE_COMMAND ${CMAKE_COMMAND} ${PROTOBUF_SOURCES_DIR}/src/protobuf/cmake -Dprotobuf_BUILD_TESTS=OFF diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index 2354f555db..7b66cb44e4 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -19,8 +19,8 @@ IF((NOT ${PYTHONINTERP_FOUND}) OR (NOT ${PYTHONLIBS_FOUND})) INCLUDE(ExternalProject) - SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/python) - SET(PYTHON_INSTALL_DIR ${PROJECT_BINARY_DIR}/python) + SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/Python) + SET(PYTHON_INSTALL_DIR ${PROJECT_BINARY_DIR}/Python) IF(MSVC) LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_ARGS @@ -87,6 +87,6 @@ IF((NOT ${PYTHONINTERP_FOUND}) OR (NOT ${PYTHONLIBS_FOUND})) MESSAGE(FATAL_ERROR "Unknown system !") ENDIF() -INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) - ENDIF() + +INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 4fdd47acdb..d59e5e9c3a 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -17,7 +17,7 @@ INCLUDE(ExternalProject) SET(WARPCTC_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/warpctc) SET(WARPCTC_INSTALL_DIR ${PROJECT_BINARY_DIR}/warpctc) -IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") +IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" ) SET(USE_OMP ON) ELSE() SET(USE_OMP OFF) @@ -28,8 +28,9 @@ ExternalProject_Add( GIT_REPOSITORY "https://github.com/gangliao/warp-ctc.git" PREFIX ${WARPCTC_SOURCES_DIR} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${WARPCTC_INSTALL_DIR} - CMAKE_ARGS -DWITH_GPU=${CUDA_FOUND} + CMAKE_ARGS -DWITH_GPU=${WITH_GPU} CMAKE_ARGS -DWITH_OMP=${USE_OMP} + UPDATE_COMMAND "" ) SET(WARPCTC_INCLUDE_DIR "${WARP_INSTALL_DIR}/include" CACHE PATH "Warp-ctc Directory" FORCE) @@ -40,7 +41,7 @@ IF(WIN32) "${WARPCTC_INSTALL_DIR}/lib/warpctc.dll" CACHE FILEPATH "Warp-ctc Library" FORCE) ELSE(WIN32) SET(WARPCTC_LIBRARIES - "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.so" CACHE FILEPATH "Warp-ctc Library" FORCE) + "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.dylib" CACHE FILEPATH "Warp-ctc Library" FORCE) ENDIF(WIN32) LIST(APPEND external_project_dependencies warpctc) From de3c15277f2b08e88c3b7f84156064fef0831b90 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 23:25:32 +0800 Subject: [PATCH 059/119] Fix glog check type unmatch --- paddle/function/cross_map_normal_op.cpp | 16 ++++++++-------- paddle/gserver/layers/ConvProjection.cpp | 3 ++- paddle/gserver/tests/LayerGradUtil.cpp | 2 +- paddle/gserver/tests/test_BatchNorm.cpp | 4 ++-- paddle/gserver/tests/test_PyDataProvider2.cpp | 2 +- paddle/math/Matrix.cpp | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/paddle/function/cross_map_normal_op.cpp b/paddle/function/cross_map_normal_op.cpp index a9c7693830..74094bc4fc 100644 --- a/paddle/function/cross_map_normal_op.cpp +++ b/paddle/function/cross_map_normal_op.cpp @@ -128,11 +128,11 @@ public: void calc(const Arguments& inputs, const Arguments& outputs, const Arguments& inouts) override { - CHECK_EQ(1, inputs.size()); - CHECK_EQ(2, outputs.size()); - CHECK_EQ(0, inouts.size()); + CHECK_EQ(1, static_cast(inputs.size())); + CHECK_EQ(2, static_cast(outputs.size())); + CHECK_EQ(0, static_cast(inouts.size())); - CHECK_EQ(inputs[0].dims_.size(), 4); + CHECK_EQ(static_cast(inputs[0].dims_.size()), 4); for (size_t i = 0; i < inputs[0].dims_.size(); i++) { CHECK_EQ(inputs[0].dims_[i], outputs[0].dims_[i]); CHECK_EQ(inputs[0].dims_[i], outputs[1].dims_[i]); @@ -180,11 +180,11 @@ public: void calc(const Arguments& inputs, const Arguments& outputs, const Arguments& inouts) override { - CHECK_EQ(4, inputs.size()); - CHECK_EQ(1, outputs.size()); - CHECK_EQ(0, inouts.size()); + CHECK_EQ(4, static_cast(inputs.size())); + CHECK_EQ(1, static_cast(outputs.size())); + CHECK_EQ(0, static_cast(inouts.size())); - CHECK_EQ(inputs[0].dims_.size(), 4); + CHECK_EQ(static_cast(inputs[0].dims_.size()), 4); for (size_t i = 0; i < inputs[0].dims_.size(); i++) { CHECK_EQ(inputs[0].dims_[i], inputs[1].dims_[i]); CHECK_EQ(inputs[0].dims_[i], inputs[2].dims_[i]); diff --git a/paddle/gserver/layers/ConvProjection.cpp b/paddle/gserver/layers/ConvProjection.cpp index e1c4b91ace..0281170bc5 100644 --- a/paddle/gserver/layers/ConvProjection.cpp +++ b/paddle/gserver/layers/ConvProjection.cpp @@ -130,7 +130,8 @@ void ConvProjection::reshapeTensorDesc(int batchSize) { void ConvProjection::reshape(int batchSize) { size_t width = calOutputSize(); CHECK_EQ(width, out_->value->getWidth()); - CHECK_EQ(channels_ * imageH_ * imageW_, in_->value->getWidth()) + CHECK_EQ(static_cast(channels_ * imageH_ * imageW_), + in_->value->getWidth()) << "Wrong input size for convolution" << " channels=" << channels_ << " imageH=" << imageH_ << " imageW=" << imageW_ << " inputSize=" << in_->value->getWidth(); diff --git a/paddle/gserver/tests/LayerGradUtil.cpp b/paddle/gserver/tests/LayerGradUtil.cpp index 57c176810f..ae016e74ea 100644 --- a/paddle/gserver/tests/LayerGradUtil.cpp +++ b/paddle/gserver/tests/LayerGradUtil.cpp @@ -310,7 +310,7 @@ void initDataLayer(TestConfig testConf, testConf.inputDefs[i].labelSeqStartPositions; if (labelSeqStartPositions.size() != 0) { CHECK(!sequenceStartPositions); - CHECK_GE(labelSeqStartPositions.size(), 2); + CHECK_GE(static_cast(labelSeqStartPositions.size()), 2); sequenceStartPositions = ICpuGpuVector::create(labelSeqStartPositions.size(), useGpu); diff --git a/paddle/gserver/tests/test_BatchNorm.cpp b/paddle/gserver/tests/test_BatchNorm.cpp index 7f5fcb670b..e000c69944 100644 --- a/paddle/gserver/tests/test_BatchNorm.cpp +++ b/paddle/gserver/tests/test_BatchNorm.cpp @@ -114,8 +114,8 @@ TEST(Layer, batchNorm) { bnLayer->forward(PASS_GC); convLayer->forward(PASS_GC); - CHECK_EQ(convLayer->getOutputValue()->getHeight(), 100); - CHECK_EQ(convLayer->getOutputValue()->getWidth(), 576); + CHECK_EQ(static_cast(convLayer->getOutputValue()->getHeight()), 100); + CHECK_EQ(static_cast(convLayer->getOutputValue()->getWidth()), 576); } int main(int argc, char** argv) { diff --git a/paddle/gserver/tests/test_PyDataProvider2.cpp b/paddle/gserver/tests/test_PyDataProvider2.cpp index 5f8bc5ecd0..7e193eb31a 100644 --- a/paddle/gserver/tests/test_PyDataProvider2.cpp +++ b/paddle/gserver/tests/test_PyDataProvider2.cpp @@ -293,7 +293,7 @@ TEST(PyDataProvider2, can_over_batch_size) { while (true) { int64_t realBatchSize = provider->getNextBatchInternal(batchSize, &batch); if (realBatchSize) { - CHECK_LE(realBatchSize, batchSize); + CHECK_LE(static_cast(realBatchSize), batchSize); } else { break; } diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 50d2e3eb67..b281d5eb02 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -2268,7 +2268,7 @@ void CpuMatrix::contextProjectionBackward(Matrix* inputGrad, int64_t inputDim = inputGrad ? inputGrad->getWidth() : weightGrad ? weightGrad->getWidth() : 0; - CHECK_EQ(getWidth(), inputDim * contextLength); + CHECK_EQ(getWidth(), static_cast(inputDim * contextLength)); const int* starts = sequence.getData(); size_t numSequences = sequence.getSize() - 1; From 0b956711d994d101f7e68aa59b586d2f21645195 Mon Sep 17 00:00:00 2001 From: liaogang Date: Tue, 27 Dec 2016 23:25:50 +0800 Subject: [PATCH 060/119] Add external_project_dependencies for targets --- cmake/util.cmake | 1 + paddle/cuda/CMakeLists.txt | 2 ++ paddle/cuda/include/hl_warpctc_wrap.h | 2 +- paddle/function/CMakeLists.txt | 2 ++ proto/CMakeLists.txt | 8 ++++---- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cmake/util.cmake b/cmake/util.cmake index 1b1e630661..b8d20266f4 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -121,6 +121,7 @@ function(link_paddle_exe TARGET_NAME) target_link_libraries(${TARGET_NAME} rt) endif() endif() + add_dependencies(${TARGET_NAME} ${external_project_dependencies}) endfunction() # link_paddle_test diff --git a/paddle/cuda/CMakeLists.txt b/paddle/cuda/CMakeLists.txt index aa1ff4a771..57fb89608f 100755 --- a/paddle/cuda/CMakeLists.txt +++ b/paddle/cuda/CMakeLists.txt @@ -88,6 +88,8 @@ else() ${CUDA_CXX_SOURCES}) endif() +add_dependencies(paddle_cuda ${external_project_dependencies}) + add_style_check_target(paddle_cuda ${CUDA_SOURCES} ${CUDA_HEADERS} diff --git a/paddle/cuda/include/hl_warpctc_wrap.h b/paddle/cuda/include/hl_warpctc_wrap.h index 79bf6c3db7..7885ae5701 100644 --- a/paddle/cuda/include/hl_warpctc_wrap.h +++ b/paddle/cuda/include/hl_warpctc_wrap.h @@ -15,8 +15,8 @@ limitations under the License. */ #ifndef HL_WARPCTC_WRAP_H_ #define HL_WARPCTC_WRAP_H_ +#include "ctc.h" #include "hl_base.h" -#include "warp-ctc/include/ctc.h" typedef ctcStatus_t hl_warpctc_status_t; typedef ctcOptions hl_warpctc_options_t; diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 0697842bbe..1de887b7dd 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -10,8 +10,10 @@ if(WITH_GPU) endif() add_library(paddle_function STATIC ${cpp_files} ${cu_objs}) +add_dependencies(paddle_function ${external_project_dependencies}) add_library(paddle_test_main STATIC TestMain.cpp) +add_dependencies(paddle_test_main ${external_project_dependencies}) if(WITH_GPU) # TODO: diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 2c40070eca..c4e170b10f 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -20,8 +20,8 @@ foreach(filename ${proto_filenames}) add_custom_command(OUTPUT ${CUR_PROTO_GEN} COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --cpp_out ${CMAKE_CURRENT_BINARY_DIR} - --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} - DEPENDS ${filename}) + --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} + DEPENDS ${filename} ${external_project_dependencies}) set(CUR_PROTO_GEN_PY ${PROJ_ROOT}/paddle/python/paddle/proto/${base_filename}_pb2.py) @@ -30,8 +30,8 @@ foreach(filename ${proto_filenames}) ${PROTO_GEN_PY}) add_custom_command(OUTPUT ${CUR_PROTO_GEN_PY} COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --python_out ${PROJ_ROOT}/python/paddle/proto - --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} - DEPENDS ${filename}) + --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} + DEPENDS ${filename} ${external_project_dependencies}) endforeach() include_directories(${CMAKE_CURRENT_BINARY_DIR}/proto) From 338dc3ee923e0757420b6466f91dc60d19850b2a Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Dec 2016 00:20:15 +0800 Subject: [PATCH 061/119] Fix external denpendencies build order --- cmake/external/numpy.cmake | 2 ++ cmake/external/protobuf.cmake | 12 +++++++++--- cmake/external/python.cmake | 3 +++ cmake/external/swig.cmake | 7 ++++++- cmake/external/warpctc.cmake | 8 +++++++- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/cmake/external/numpy.cmake b/cmake/external/numpy.cmake index 9d686ecaac..d01cff9722 100644 --- a/cmake/external/numpy.cmake +++ b/cmake/external/numpy.cmake @@ -59,6 +59,8 @@ IF(NOT ${NUMPY_FOUND}) DEPENDS python setuptools cython ) + LIST(APPEND external_project_dependencies numpy) + # find numpy include directory FILE(WRITE ${PROJECT_BINARY_DIR}/FindNumpyPath.py "try: import numpy; print(numpy.get_include())\nexcept:pass\n") diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 0138f082d9..efd07eb807 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -42,10 +42,16 @@ IF(WIN32) "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.lib" CACHE FILEPATH "protobuf libraries." FORCE) SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc.exe" CACHE FILEPATH "protobuf executable." FORCE) ELSE(WIN32) + FIND_PATH(PROTOBUF_LIBS_DIR libprotoc.a + PATHS + ${PROTOBUF_INSTALL_DIR}/lib64 + ${PROTOBUF_INSTALL_DIR}/lib + NO_DEFAULT_PATH + ) SET(PROTOBUF_LIBRARIES - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite.a" - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf.a" - "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.a" CACHE FILEPATH "protobuf libraries." FORCE) + "${PROTOBUF_LIBS_DIR}/libprotobuf-lite.a" + "${PROTOBUF_LIBS_DIR}/libprotobuf.a" + "${PROTOBUF_LIBS_DIR}/libprotoc.a" CACHE FILEPATH "protobuf libraries." FORCE) SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc" CACHE FILEPATH "protobuf executable." FORCE) ENDIF(WIN32) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index 7b66cb44e4..d6cdf535fe 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -87,6 +87,9 @@ IF((NOT ${PYTHONINTERP_FOUND}) OR (NOT ${PYTHONLIBS_FOUND})) MESSAGE(FATAL_ERROR "Unknown system !") ENDIF() +LIST(APPEND external_project_dependencies python) + ENDIF() INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) + diff --git a/cmake/external/swig.cmake b/cmake/external/swig.cmake index 1ec61660bc..2da826d375 100644 --- a/cmake/external/swig.cmake +++ b/cmake/external/swig.cmake @@ -70,6 +70,9 @@ IF(NOT ${SWIG_FOUND}) set(SWIG_DIR ${SWIG_INSTALL_DIR}/share/swig/${SWIG_TARGET_VERSION} CACHE FILEPATH "SWIG Directory" FORCE) set(SWIG_EXECUTABLE ${SWIG_INSTALL_DIR}/bin/swig CACHE FILEPATH "SWIG Executable" FORCE) ENDIF(WIN32) + + LIST(APPEND external_project_dependencies swig) + ENDIF() FUNCTION(generate_python_api target_name) @@ -80,10 +83,12 @@ FUNCTION(generate_python_api target_name) && mv ${PROJ_ROOT}/paddle/swig_paddle.py ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py DEPENDS ${PROJ_ROOT}/paddle/api/Paddle.swig ${PROJ_ROOT}/paddle/api/PaddleAPI.h + ${external_project_dependencies} WORKING_DIRECTORY ${PROJ_ROOT}/paddle COMMENT "Generate Python API from swig") ADD_CUSTOM_TARGET(${target_name} ALL DEPENDS ${PROJ_ROOT}/paddle/Paddle_wrap.cxx ${PROJ_ROOT}/paddle/Paddle_wrap.h - ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py) + ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py + ${external_project_dependencies}) ENDFUNCTION(generate_python_api) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index d59e5e9c3a..2e678aadcf 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -40,8 +40,14 @@ IF(WIN32) SET(WARPCTC_LIBRARIES "${WARPCTC_INSTALL_DIR}/lib/warpctc.dll" CACHE FILEPATH "Warp-ctc Library" FORCE) ELSE(WIN32) + IF(APPLE) + SET(_warpctc_SHARED_SUFFIX dylib) + ELSE(APPLE) + SET(_warpctc_SHARED_SUFFIX so) + ENDIF(APPLE) + SET(WARPCTC_LIBRARIES - "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.dylib" CACHE FILEPATH "Warp-ctc Library" FORCE) + "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.${_warpctc_SHARED_SUFFIX}" CACHE FILEPATH "Warp-ctc Library" FORCE) ENDIF(WIN32) LIST(APPEND external_project_dependencies warpctc) From 647f9a0f4e076121012633c33067febb96038284 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Dec 2016 00:35:08 +0800 Subject: [PATCH 062/119] do not init localUpdater --- paddle/api/ParameterUpdater.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index e84bb63866..33aa11850c 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -32,8 +32,7 @@ ParameterUpdater *ParameterUpdater::createRemoteUpdater( OptimizationConfig *config, int passCount) { auto updater = new ParameterUpdater(); std::unique_ptr localUpdater; - localUpdater.reset(new paddle::SgdThreadUpdater(config->m->getConfig())); - updater->m->updater.reset(new paddle::ConcurrentRemoteParameterUpdater( + updater->m->updater.reset(new paddle::RemoteParameterUpdater( config->m->getConfig(), passCount, std::move(localUpdater))); return updater; } From 2ffb6dbb171b426ef85db88df4f5ac2493abb8d8 Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Dec 2016 09:58:54 +0800 Subject: [PATCH 063/119] Fix warpctc header directory --- cmake/external/warpctc.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 2e678aadcf..b3dea19ceb 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -33,7 +33,7 @@ ExternalProject_Add( UPDATE_COMMAND "" ) -SET(WARPCTC_INCLUDE_DIR "${WARP_INSTALL_DIR}/include" CACHE PATH "Warp-ctc Directory" FORCE) +SET(WARPCTC_INCLUDE_DIR "${WARPCTC_INSTALL_DIR}/include" CACHE PATH "Warp-ctc Directory" FORCE) INCLUDE_DIRECTORIES(${WARPCTC_INCLUDE_DIR}) IF(WIN32) From aee0857838ee41f46237f0b6561242d523116a0c Mon Sep 17 00:00:00 2001 From: liaogang Date: Wed, 28 Dec 2016 13:49:01 +0800 Subject: [PATCH 064/119] Clean Travis ci and fix bug --- .gitmodules | 3 --- .pre-commit-config.yaml | 4 ++-- .travis.yml | 6 ----- CMakeLists.txt | 1 - cmake/external/gflags.cmake | 1 + cmake/external/glog.cmake | 1 + cmake/external/gtest.cmake | 1 + cmake/external/protobuf.cmake | 23 +++++++++++-------- cmake/external/warpctc.cmake | 1 + paddle/api/paddle_api_config.py.in | 10 ++++---- paddle/api/paddle_ld_flags.py | 6 ++--- paddle/cuda/CMakeLists.txt | 2 +- paddle/gserver/tests/CMakeLists.txt | 2 +- paddle/scripts/travis/before_install.linux.sh | 18 --------------- paddle/scripts/travis/before_install.osx.sh | 8 +------ paddle/scripts/travis/build_submodules.sh | 20 ---------------- 16 files changed, 30 insertions(+), 77 deletions(-) delete mode 100644 .gitmodules delete mode 100755 paddle/scripts/travis/before_install.linux.sh delete mode 100755 paddle/scripts/travis/build_submodules.sh diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f635e65784..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "warp-ctc"] - path = warp-ctc - url = https://github.com/baidu-research/warp-ctc.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9902a863d..a6e45028eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ sha: c25201a00e6b0514370501050cf2a8538ac12270 hooks: - id: remove-crlf - files: (?!.*warp-ctc)^.*$ + files: (?!.*third_party)^.*$ - repo: https://github.com/reyoung/mirrors-yapf.git sha: v0.13.2 hooks: @@ -15,7 +15,7 @@ - id: check-merge-conflict - id: check-symlinks - id: detect-private-key - files: (?!.*warp-ctc)^.*$ + files: (?!.*third_party)^.*$ - id: end-of-file-fixer - repo: https://github.com/PaddlePaddle/clang-format-pre-commit-hook.git sha: 28c0ea8a67a3e2dbbf4822ef44e85b63a0080a29 diff --git a/.travis.yml b/.travis.yml index 047ca6ffe7..b49d4638d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,16 +24,11 @@ addons: - wget - git - build-essential - - libatlas-base-dev - python - python-pip - python2.7-dev - - m4 - python-numpy - python-wheel - - libgoogle-glog-dev - - libgflags-dev - - libgtest-dev - curl - lcov - graphviz @@ -53,7 +48,6 @@ before_install: fi fi fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo paddle/scripts/travis/before_install.linux.sh; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then paddle/scripts/travis/before_install.osx.sh; fi - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi - pip install wheel protobuf sphinx recommonmark virtualenv numpy sphinx_rtd_theme pre-commit requests==2.9.2 LinkChecker diff --git a/CMakeLists.txt b/CMakeLists.txt index 7db4c9f1d1..784876f089 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,6 @@ set(EXTERNAL_LIBS ${GLOG_LIBRARIES} ${CBLAS_LIBRARIES} ${PROTOBUF_LIBRARIES} - ${WARPCTC_LIBRARIES} ${ZLIB_LIBRARIES} ) diff --git a/cmake/external/gflags.cmake b/cmake/external/gflags.cmake index 128d50cec8..55f9a4c3e6 100644 --- a/cmake/external/gflags.cmake +++ b/cmake/external/gflags.cmake @@ -22,6 +22,7 @@ ExternalProject_Add( GIT_REPOSITORY "https://github.com/gflags/gflags.git" PREFIX ${GFLAGS_SOURCES_DIR} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${GFLAGS_INSTALL_DIR} + CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON CMAKE_ARGS -DBUILD_TESTING=OFF LOG_DOWNLOAD =ON UPDATE_COMMAND "" diff --git a/cmake/external/glog.cmake b/cmake/external/glog.cmake index 8a4b9d5996..473071a72a 100644 --- a/cmake/external/glog.cmake +++ b/cmake/external/glog.cmake @@ -22,6 +22,7 @@ ExternalProject_Add( GIT_REPOSITORY "https://github.com/google/glog.git" PREFIX ${GLOG_SOURCES_DIR} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${GLOG_INSTALL_DIR} + CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON CMAKE_ARGS -DWITH_GFLAGS=OFF CMAKE_ARGS -DBUILD_TESTING=OFF LOG_DOWNLOAD =ON diff --git a/cmake/external/gtest.cmake b/cmake/external/gtest.cmake index 533104422a..a6ed9e9b9f 100644 --- a/cmake/external/gtest.cmake +++ b/cmake/external/gtest.cmake @@ -23,6 +23,7 @@ ExternalProject_Add( GIT_TAG "release-1.8.0" PREFIX ${GTEST_SOURCES_DIR} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GTEST_INSTALL_DIR} + CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON CMAKE_ARGS -DBUILD_GMOCK=ON CMAKE_ARGS -Dgtest_disable_pthreads=ON CMAKE_ARGS -Dgtest_force_shared_crt=ON diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index efd07eb807..f42e42ef68 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -36,22 +36,25 @@ SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" CACHE PATH "protobuf INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) IF(WIN32) - SET(PROTOBUF_LIBRARIES - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite.lib" - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf.lib" - "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.lib" CACHE FILEPATH "protobuf libraries." FORCE) + SET(PROTOBUF_LITE_LIBRARY + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite.lib" CACHE FILEPATH "protobuf lite library." FORCE) + SET(PROTOBUF_LIBRARY + "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf.lib" CACHE FILEPATH "protobuf library." FORCE) + SET(PROTOBUF_PROTOC_LIBRARY + "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.lib" CACHE FILEPATH "protoc library." FORCE) SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc.exe" CACHE FILEPATH "protobuf executable." FORCE) ELSE(WIN32) FIND_PATH(PROTOBUF_LIBS_DIR libprotoc.a - PATHS - ${PROTOBUF_INSTALL_DIR}/lib64 ${PROTOBUF_INSTALL_DIR}/lib + ${PROTOBUF_INSTALL_DIR}/lib64 NO_DEFAULT_PATH ) - SET(PROTOBUF_LIBRARIES - "${PROTOBUF_LIBS_DIR}/libprotobuf-lite.a" - "${PROTOBUF_LIBS_DIR}/libprotobuf.a" - "${PROTOBUF_LIBS_DIR}/libprotoc.a" CACHE FILEPATH "protobuf libraries." FORCE) + SET(PROTOBUF_LITE_LIBRARY + "${PROTOBUF_LIBS_DIR}/libprotobuf-lite.a" CACHE FILEPATH "protobuf lite library." FORCE) + SET(PROTOBUF_LIBRARY + "${PROTOBUF_LIBS_DIR}/libprotobuf.a" CACHE FILEPATH "protobuf library." FORCE) + SET(PROTOBUF_PROTOC_LIBRARY + "${PROTOBUF_LIBS_DIR}/libprotoc.a" CACHE FILEPATH "protoc library." FORCE) SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc" CACHE FILEPATH "protobuf executable." FORCE) ENDIF(WIN32) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index b3dea19ceb..6a88c87df6 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -36,6 +36,7 @@ ExternalProject_Add( SET(WARPCTC_INCLUDE_DIR "${WARPCTC_INSTALL_DIR}/include" CACHE PATH "Warp-ctc Directory" FORCE) INCLUDE_DIRECTORIES(${WARPCTC_INCLUDE_DIR}) +SET(WARPCTC_LIB_DIR "${WARPCTC_INSTALL_DIR}/lib" CACHE PATH "Warp-ctc Library Directory" FORCE) IF(WIN32) SET(WARPCTC_LIBRARIES "${WARPCTC_INSTALL_DIR}/lib/warpctc.dll" CACHE FILEPATH "Warp-ctc Library" FORCE) diff --git a/paddle/api/paddle_api_config.py.in b/paddle/api/paddle_api_config.py.in index 23542b952b..e11ee92036 100644 --- a/paddle/api/paddle_api_config.py.in +++ b/paddle/api/paddle_api_config.py.in @@ -1,17 +1,17 @@ PADDLE_BUILD_DIR="@CMAKE_CURRENT_BINARY_DIR@/../" WITH_GPU="@WITH_GPU@" -PROTOBUF_LIB="@PROTOBUF_LIBRARY@" -ZLIB_LIB="@ZLIB_LIBRARIES@" +PROTOBUF_LIBRARY="@PROTOBUF_LIBRARY@" +ZLIB_LIBRARIES="@ZLIB_LIBRARIES@" CMAKE_THREAD_LIB="@CMAKE_THREAD_LIBS_INIT@" CMAKE_DL_LIBS="@CMAKE_DL_LIBS@" WITH_PYTHON="@WITH_PYTHON@" PYTHON_LIBRARIES="@PYTHON_LIBRARIES@" -LIBGLOG_LIBRARY="@LIBGLOG_LIBRARY@" +GLOG_LIBRARIES="@GLOG_LIBRARIES@" GFLAGS_LIBRARIES="@GFLAGS_LIBRARIES@" GFLAGS_LOCATION="@GFLAGS_LOCATION@" -CBLAS_LIBRARIES="@CBLAS_LIBS@" +CBLAS_LIBRARIES="@CBLAS_LIBRARIES@" -CUDA_LIBRARIES="@CUDA_LIBRARIES@" +CUDA_LIBRARIES="@CUDA_cudart_shared_LIBRARY@" WITH_COVERALLS="@ON_COVERALLS@" diff --git a/paddle/api/paddle_ld_flags.py b/paddle/api/paddle_ld_flags.py index b4d27b1cc7..ad5dce209b 100644 --- a/paddle/api/paddle_ld_flags.py +++ b/paddle/api/paddle_ld_flags.py @@ -40,14 +40,14 @@ try: self.paddle_build_dir = PADDLE_BUILD_DIR self.paddle_build_dir = os.path.abspath(self.paddle_build_dir) self.with_gpu = PaddleLDFlag.cmake_bool(WITH_GPU) - self.protolib = PROTOBUF_LIB - self.zlib = ZLIB_LIB + self.protolib = PROTOBUF_LIBRARY + self.zlib = ZLIB_LIBRARIES self.thread = CMAKE_THREAD_LIB self.dl_libs = CMAKE_DL_LIBS self.with_python = PaddleLDFlag.cmake_bool(WITH_PYTHON) self.python_libs = PYTHON_LIBRARIES - self.glog_libs = LIBGLOG_LIBRARY + self.glog_libs = GLOG_LIBRARIES self.with_coverage = PaddleLDFlag.cmake_bool(WITH_COVERALLS) self.gflags_libs = GFLAGS_LIBRARIES diff --git a/paddle/cuda/CMakeLists.txt b/paddle/cuda/CMakeLists.txt index 57fb89608f..0a05897854 100755 --- a/paddle/cuda/CMakeLists.txt +++ b/paddle/cuda/CMakeLists.txt @@ -88,7 +88,7 @@ else() ${CUDA_CXX_SOURCES}) endif() -add_dependencies(paddle_cuda ${external_project_dependencies}) +add_dependencies(paddle_cuda warpctc) add_style_check_target(paddle_cuda ${CUDA_SOURCES} diff --git a/paddle/gserver/tests/CMakeLists.txt b/paddle/gserver/tests/CMakeLists.txt index c26a2a7f06..4190892db1 100644 --- a/paddle/gserver/tests/CMakeLists.txt +++ b/paddle/gserver/tests/CMakeLists.txt @@ -92,7 +92,7 @@ if(NOT WITH_DOUBLE) TestUtil.cpp) add_test(NAME test_WarpCTCLayer - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_WarpCTCLayer --warpctc_dir=${PROJ_ROOT}/warp-ctc/build + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_WarpCTCLayer --warpctc_dir=${WARPCTC_LIB_DIR} WORKING_DIRECTORY ${PROJ_ROOT}/paddle) endif() diff --git a/paddle/scripts/travis/before_install.linux.sh b/paddle/scripts/travis/before_install.linux.sh deleted file mode 100755 index 9620bff6bc..0000000000 --- a/paddle/scripts/travis/before_install.linux.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -e -pip install protobuf -cd /tmp -wget https://github.com/google/protobuf/archive/v3.0.2.tar.gz -O protobuf.tar.gz -tar xf protobuf.tar.gz -cd protobuf* -./autogen.sh -./configure --prefix=/usr/ -make -j 2 install -cd .. -rm -rf protobuf* - -pushd /usr/src/gtest -cmake . -make -sudo cp *.a /usr/lib -popd diff --git a/paddle/scripts/travis/before_install.osx.sh b/paddle/scripts/travis/before_install.osx.sh index bd88ed3913..89742d67f5 100755 --- a/paddle/scripts/travis/before_install.osx.sh +++ b/paddle/scripts/travis/before_install.osx.sh @@ -3,10 +3,4 @@ brew update brew tap homebrew/science brew install python sudo pip install --upgrade protobuf -brew install cmake python glog gflags openblas wget md5sha1sum protobuf - -wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz -O gtest.tar.gz -tar xf gtest.tar.gz -cd googletest-release-1.8.0/ -cmake . -make install +brew install cmake python wget md5sha1sum diff --git a/paddle/scripts/travis/build_submodules.sh b/paddle/scripts/travis/build_submodules.sh deleted file mode 100755 index d458bf92bf..0000000000 --- a/paddle/scripts/travis/build_submodules.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -e -WORK_DIR=$PWD -PROJ_ROOT=$(git rev-parse --show-cdup) -SUBMODULES=$(grep path ${PROJ_ROOT}.gitmodules | sed 's/^.*path = //') - -for module in $SUBMODULES -do - case $module in - "warp-ctc") - if [ -d ${PROJ_ROOT}warp-ctc/build ]; then - rm -rf ${PROJ_ROOT}warp-ctc/build - fi - mkdir ${PROJ_ROOT}warp-ctc/build - cd ${PROJ_ROOT}warp-ctc/build - cmake ..; make - ;; - esac -done -cd $WORK_DIR From ba3fcc5a010b942ee085550053ee8d0d1ed5f00e Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 28 Dec 2016 14:19:42 +0800 Subject: [PATCH 065/119] Check md5 when download word embedding. Fix #1008 --- demo/model_zoo/embedding/pre_DictAndModel.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/demo/model_zoo/embedding/pre_DictAndModel.sh b/demo/model_zoo/embedding/pre_DictAndModel.sh index f97ef26107..7ad3aa05e9 100755 --- a/demo/model_zoo/embedding/pre_DictAndModel.sh +++ b/demo/model_zoo/embedding/pre_DictAndModel.sh @@ -14,9 +14,20 @@ # limitations under the License. set -e set -x +BASE_URL='http://paddlepaddle.cdn.bcebos.com/model_zoo/embedding' -# download the dictionary and pretrained model -for file in baidu.dict model_32.emb model_64.emb model_128.emb model_256.emb -do - wget http://paddlepaddle.bj.bcebos.com/model_zoo/embedding/$file +wget ${BASE_URL}/baidu.dict + +DOWNLOAD_ITEMS=(model_32.emb model_64.emb model_128.emb model_256.emb) +ITEM_MD5=(f88c8325ee6da6187f1080e8fe66c1cd + 927cf70f27f860aff1a5703ebf7f1584 + a52e43655cd25d279777ed509a1ae27b + b92c67fe9ff70fea53596080e351ac80) + +for ((i=0; i<${#ITEM_MD5[@]}; i++)) +do + FILENAME=${DOWNLOAD_ITEMS[${i}]} + REAL_MD5=`wget ${BASE_URL}/${FILENAME} -O - | tee ${FILENAME} | md5sum | cut -d ' ' -f 1` + EXPECTED_MD5=${ITEM_MD5[${i}]} + [ "${EXPECTED_MD5}" = "${REAL_MD5}" ] done From cee99447fd8fbb20244a9cb73c9b48d0c2facc50 Mon Sep 17 00:00:00 2001 From: qiaolongfei Date: Wed, 28 Dec 2016 14:29:47 +0800 Subject: [PATCH 066/119] udw nullptr as localUpdater --- paddle/api/ParameterUpdater.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/paddle/api/ParameterUpdater.cpp b/paddle/api/ParameterUpdater.cpp index 33aa11850c..75b0ae7cb6 100644 --- a/paddle/api/ParameterUpdater.cpp +++ b/paddle/api/ParameterUpdater.cpp @@ -31,9 +31,8 @@ ParameterUpdater *ParameterUpdater::createLocalUpdater( ParameterUpdater *ParameterUpdater::createRemoteUpdater( OptimizationConfig *config, int passCount) { auto updater = new ParameterUpdater(); - std::unique_ptr localUpdater; updater->m->updater.reset(new paddle::RemoteParameterUpdater( - config->m->getConfig(), passCount, std::move(localUpdater))); + config->m->getConfig(), passCount, nullptr)); return updater; } From 2b174a04bf0cb266fd455842a79e25de67818672 Mon Sep 17 00:00:00 2001 From: Zhizhong Su Date: Wed, 28 Dec 2016 08:34:35 +0000 Subject: [PATCH 067/119] add new_layer_cn doc --- doc/howto/dev/new_layer_cn.rst | 390 +++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 doc/howto/dev/new_layer_cn.rst diff --git a/doc/howto/dev/new_layer_cn.rst b/doc/howto/dev/new_layer_cn.rst new file mode 100644 index 0000000000..ebec5fa206 --- /dev/null +++ b/doc/howto/dev/new_layer_cn.rst @@ -0,0 +1,390 @@ +================ +实现新的网络层 +================ + +这份教程指导你在PaddlePaddle中实现一个自定义的网络层。在这里我们使用全连接层作为例子来指导你完成实现新网络层需要的几个步骤。 + +- 推导该层前向和后向传递的方程。 +- 实现该层的C++类。 +- 写梯度检测的测试单元,以保证梯度的正确计算。 +- 实现该层的python封装。 + +推导方程 +================ + +首先我们需要推导该网络层的*前向传播*和*后向传播*的方程。前向传播给定输入,计算输出。后向传播给定输出的梯度,计算输入和参数的梯度。 + +下图是一个全链接层的示意图。在全连接层中,每个输出节点都连接到所有的输入节点上。 + +.. image:: FullyConnected.jpg + :align: center + :scale: 60 % + +一个网络层的前向传播部分把输入转化为相应的输出。 +全连接层以一个维度为:math:`D_i`稠密的向量作为输入。其用一个尺度为:math:`D_i \times D_o`的变换矩阵:math:`W`把:math:`x`映射到一个维度为:math:`D_o`的向量,并在其上再加上维度为:math:`D_o`的偏置向量:math:`b`。 + +.. math:: + + y = f(W^T x + b) + +其中:math:`f(.)`是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。 + +变换矩阵:math:`W`和偏置向量:math:`b`是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传播对所有的参数和输入都计算输出函数的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 + +假设我们的损失函数是:math:`c(y)`,那么 + +.. math:: + + \frac{\partial c(y)}{\partial x} = \frac{\partial c(y)}{\partial y} \frac{\partial y}{\partial x} + +假设:math:`z = f(W^T x + b)`,那么 + +.. math:: + + \frac{\partial y}{\partial z} = \frac{\partial f(z)}{\partial z} + +我们的base layer类可以自动计算上面的导数。 + +因而,对全连接层来说,我们需要计算: + +.. math:: + + \frac{\partial z}{\partial x} = W, \frac{\partial z_j}{\partial W_{ij}} = x_i, \frac{\partial z}{\partial b} = \mathbf 1 + +其中:math:`\mathbf 1`是一个全1的向量,:math:`W_{ij}`是矩阵:math:`W`第i行第j列的数值,:math:`z_j`是向量math:`z`的第j个值,:math:`x_i`是向量:math:`x`的第i个值。 + +最后我们使用链式法则计算:math:`\frac{\partial z}{\partial x}`以及:math:`\frac{\partial z}{\partial W}`。计算的细节将在下面的小节给出。 + +实现C++类 +=================== + +一个网络层的C++类需要实现初始化,前向和后向。全连接层的实现位于:code:`paddle/gserver/layers/FullyConnectedLayer.h`及:code:`paddle/gserver/layers/FullyConnectedLayer.cpp`。这里我们展示一份简化过的代码。 + +这个类需要继承:code:`paddle::Layer`这个基类,并且需要重写以下基类中的虚函数: + +- 类的构造函数和析构析构函数。 +- :code:`init`函数。用于初始化参数和设置。 +- :code:`forward`。实现网络层的前向传播。 +- :code:`backward`。实现网络层的后向传播。 +- :code:`prefetch`。用于确定由参数服务器预取的行相关的参数矩阵。如果该网络层不需要远程稀疏更新的话,你不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) + + +头文件在下面列出: + +.. code-block:: c++ + + namespace paddle { + /** + * 全连接层的每个输出都连接到上一层的所有的神经元上。 + * 其用一些学习过的参数做内积并加上偏置(可选)。 + * + * 配置文件接口是fc_layer。 + */ + + class FullyConnectedLayer : public Layer { + protected: + WeightList weights_; + std::unique_ptr biases_; + + public: + explicit FullyConnectedLayer(const LayerConfig& config) + : Layer(config) {} + ~FullyConnectedLayer() {} + + bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); + + Weight& getWeight(int idx) { return *weights_[idx]; } + + void prefetch(); + void forward(PassType passType); + void backward(const UpdateCallback& callback = nullptr); + }; + } // namespace paddle + +头文件中把参数定位为类的成员变量。我们使用:code:`Weight`类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中由详细介绍。 + +- :code:`weights_`是存有变换矩阵的一系列权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 +- :code:`biases_`是存有偏置向量的权重。 + +全连接层没有网络层配置的超参数。如果一个网络层需要配置的话,通常的做法是将配置存于:code:`LayerConfig& config`中,并在类构建函数中把它放入一个类成员变量里。 + +下面的代码片段实现了:code:`init`函数。 + +- 首先,所有的:code:`init`函数必须先调用基类中的函数:code:`Layer::init(layerMap, parameterMap);`。该语句会为每个层初始化其所需要的变量和连接。 +- 之后初始化所有的权重矩阵:math:`W`。当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。 +- 最后,初始化偏置向量。 + + +.. code-block:: c++ + + bool FullyConnectedLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + /* 初始化父类 */ + Layer::init(layerMap, parameterMap); + + /* 初始化权重表 */ + CHECK(inputLayers_.size() == parameters_.size()); + for (size_t i = 0; i < inputLayers_.size(); i++) { + // 获得参数尺寸 + size_t height = inputLayers_[i]->getSize(); + size_t width = getSize(); + + // 新建一个权重 + if (parameters_[i]->isSparse()) { + CHECK_LE(parameters_[i]->getSize(), width * height); + } else { + CHECK_EQ(parameters_[i]->getSize(), width * height); + } + Weight* w = new Weight(height, width, parameters_[i]); + + // 将新建的权重加入权重表 + weights_.emplace_back(w); + } + + /* 初始化biases_ */ + if (biasParameter_.get() != NULL) { + biases_ = std::unique_ptr(new Weight(1, getSize(), biasParameter_)); + } + + return true; + } + +实现前向传播的部分有下面几个步骤。 + +- 每个层在其:code:`forward`函数的开头必须调用:code:`Layer::forward(passType);`。 +- 之后使用:code:`reserveOutput(batchSize, size);`为输出分配内存。由于我们支持训练数据有不同的批次大小,所以这一步是必要的。:code:`reserveOutput` 会相应地改变输出的尺寸。为了保证效率,如果需要扩大矩阵,我们会重新分配内存;如果需要缩减矩阵,我们会继续使用现有的内存块。 +- 之后使用矩阵运算函数来计算:math:`\sum_i W_i x + b`。:code:`getInput(i).value`返回第i个输入矩阵。每个输入都是一个:math:`batchSize \times dim`的矩阵,每行表示一个批次中的单个输入。对于我们支持的全部矩阵操作,请参考:code:`paddle/math/Matrix.h`和:code:`paddle/math/BaseMatrix.h`。 +- 最终,使用:code:`forwardActivation();`进行激活操作。这会自动进行网络配置中声明的激活操作。 + + +.. code-block:: c++ + + void FullyConnectedLayer::forward(PassType passType) { + Layer::forward(passType); + + /* 若有必要,为output_申请内存 */ + int batchSize = getInput(0).getBatchSize(); + int size = getSize(); + + { + // 设置输出的尺寸 + reserveOutput(batchSize, size); + } + + MatrixPtr outV = getOutputValue(); + + // 对每个输入乘上转化矩阵 + for (size_t i = 0; i != inputLayers_.size(); ++i) { + auto input = getInput(i); + CHECK(input.value) << "The input of 'fc' layer must be matrix"; + i == 0 ? outV->mul(input.value, weights_[i]->getW(), 1, 0) + : outV->mul(input.value, weights_[i]->getW(), 1, 1); + } + + /* 加上偏置向量 */ + if (biases_.get() != NULL) { + outV->addBias(*(biases_->getW()), 1); + } + + /* 激活 */ { + forwardActivation(); + } + } + +实现后向传播的部分有下面几个步骤。 + +- :code:`backwardActivation()`计算激活函数的梯度。梯度会就地(不使用额外空间)乘上输出的梯度,并可以通过:code:`getOutputGrad()`来获得。 +- 计算偏置的梯度。注意,我们使用:code:`biases_->getWGrad()`来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用:code:`getParameterPtr()->incUpdate(callback);`。这是用来在多线程和多机上更新参数的。 +- 之后,计算转换矩阵和输入的梯度,并对相应的参数调用:code:`incUpdate`。这给了框架一个机会去了解自己是否已经把所有的梯度收集到一个参数中,使得框架可以进行有时间重叠的工作。(例如,网络通信) + + +.. code-block:: c++ + + void FullyConnectedLayer::backward(const UpdateCallback& callback) { + /* 对激活求导 */ { + backwardActivation(); + } + + if (biases_ && biases_->getWGrad()) { + biases_->getWGrad()->collectBias(*getOutputGrad(), 1); + + /* 加上偏置的梯度 */ + biases_->getParameterPtr()->incUpdate(callback); + } + + bool syncFlag = hl_get_sync_flag(); + + for (size_t i = 0; i != inputLayers_.size(); ++i) { + /* 计算当前层权重的梯度 */ + if (weights_[i]->getWGrad()) { + MatrixPtr input_T = getInputValue(i)->getTranspose(); + MatrixPtr oGrad = getOutputGrad(); + { + weights_[i]->getWGrad()->mul(input_T, oGrad, 1, 1); + } + } + + + /* 计算输入层的偏差 */ + MatrixPtr preGrad = getInputGrad(i); + if (NULL != preGrad) { + MatrixPtr weights_T = weights_[i]->getW()->getTranspose(); + preGrad->mul(getOutputGrad(), weights_T, 1, 1); + } + + { + weights_[i]->getParameterPtr()->incUpdate(callback); + } + } + } + +:code:`prefetch`函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。在远程稀疏训练时,完整的参数矩阵被分布式的保存在参数服务器上。当网络层用一个批次做训练时,该批次中,输入仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的转换矩阵的那些行。:code:`prefetch`表明了这些行的标号。 + +大多数层不需要远程稀疏训练函数。这种情况下不需要重写该函数。 + +.. code-block:: c++ + + void FullyConnectedLayer::prefetch() { + for (size_t i = 0; i != inputLayers_.size(); ++i) { + auto* sparseParam = + dynamic_cast(weights_[i]->getW().get()); + if (sparseParam) { + MatrixPtr input = getInputValue(i); + sparseParam->addRows(input); + } + } + } + +最后,使用:code:`REGISTER_LAYER(fc, FullyConnectedLayer);`来注册该层。:code:`fc`是该层的标识符,:code:`FullyConnectedLayer`是该层的类名。 + +.. code-block:: c++ + + namespace paddle { + REGISTER_LAYER(fc, FullyConnectedLayer); + } + +若:code:`cpp`被放在:code:`paddle/gserver/layers`目录下,其会自动被加入编译列表。 + + +写梯度检查单元测试 +=============================== + +写梯度检查单元测试是一个验证新实现的层是否正确的相对简单的办法。梯度检查单元测试通过有限差分法来验证一个层的梯度。首先对输入做一个小的扰动:math:`\Delta x`,然后观察到输出的变化为:math:`\Delta y`,那么,梯度就可以通过这个方程计算得到:math:`\frac{\Delta y}{\Delta x }`。之后,再用这个梯度去和:code:`backward`函数得到的梯度去对比,以保证梯度计算的正确性。需要注意的是梯度检查仅仅验证了梯度的计算,并不保证:code:`forward`和:code:`backward`函数的实现是正确的。你需要一些更复杂的单元测试来保证你实现的网络层是正确的。 + +所有的梯度检测单侧都位于:code:`paddle/gserver/tests/test_LayerGrad.cpp`。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: + ++ 生成网络层配置。网络层配置包含以下几项: + - 偏置参数的大小。(例子中是4096) + - 层的类型。(例子中是fc) + - 层的大小。(例子中是4096) + - 激活的类型。(例子中是softmax) + - dropout的比例。(例子中是0.1) ++ 配置网络层的输入。在这个例子里,我们仅有一个输入。 + - 输入的类型(:code:`INPUT_DATA`),可以是以下几种: + - :code:`INPUT_DATA`:稠密向量。 + - :code:`INPUT_LABEL`:整数。 + - :code:`INPUT_DATA_TARGET`:稠密向量,但不用于计算梯度。 + - :code:`INPUT_SEQUENCE_DATA`:含有序列信息的稠密向量。 + - :code:`INPUT_HASSUB_SEQUENCE_DATA`:含有序列信息和子序列信息的稠密向量。 + - :code:`INPUT_SEQUENCE_LABEL`:含有序列信息的整数。 + - :code:`INPUT_SPARSE_NON_VALUE_DATA`:0-1稀疏数据。 + - :code:`INPUT_SPARSE_FLOAT_VALUE_DATA`:浮点稀疏数据。 + - 输入的名字。(例子中是:code:`layer_0`) + - 输入的大小。(例子中是8192) + - 非零数字的个数,仅对稀疏数据有效。 + - 稀疏数据的格式,仅对稀疏数据有效。 ++ 对每个输入,都需要调用一次:code:`config.layerConfig.add_inputs();`。 ++ 调用:code:`testLayerGrad`来做梯度检查。它包含下面的 It has the following arguments. + - 层和输入的配置。(例子中是:code:`config`) + - 输入的类型。(例子中是:code:`fc`) + - 梯度检查的批次大小。(例子中是100) + - 输入是否是转置的。大多数层需要设置为:code:`false`。(例子中是:code:`false`) + - 是否使用权重。有些层或者激活需要做归一化以保证它们的输出的和是一个常数。例如,softm激活的输出的和总是1。在这种情况下,我们不能通过常规的梯度检查的方式来计算梯度。因此我们采用输出的加权和(非常数)来计算梯度。(例子中是:code:`true`,因为全连接层的激活可以是softmax) + +.. code-block:: c++ + + void testFcLayer(string format, size_t nnz) { + // Create layer configuration. + TestConfig config; + config.biasSize = 4096; + config.layerConfig.set_type("fc"); + config.layerConfig.set_size(4096); + config.layerConfig.set_active_type("sigmoid"); + config.layerConfig.set_drop_rate(0.1); + // Setup inputs. + config.inputDefs.push_back( + {INPUT_DATA, "layer_0", 8192, nnz, ParaSparse(format)}); + config.layerConfig.add_inputs(); + LOG(INFO) << config.inputDefs[0].sparse.sparse << " " + << config.inputDefs[0].sparse.format; + for (auto useGpu : {false, true}) { + testLayerGrad(config, "fc", 100, /* trans */ false, useGpu, + /* weight */ true); + } + } + +如果你要为了测试而增加新的文件,例如:code:`paddle/gserver/tests/testFCGrad.cpp`,你需要把该文件加入:code:`paddle/gserver/tests/CMakeLists.txt`中。下面给出了一个例子。当你执行命令:code:`make tests`时,所有的单侧都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单侧正确执行。你需要在配置cmake时将:code:`WITH_DOUBLE`设置为`ON`。 + +.. code-block:: bash + + add_unittest_without_exec(test_FCGrad + test_FCGrad.cpp + LayerGradUtil.cpp + TestUtil.cpp) + + add_test(NAME test_FCGrad + COMMAND test_FCGrad) + + +实现python封装 +======================== + +python封装的实现使得我们可以在配置文件中使用新实现的网络层。所有的python封装都在:code:`python/paddle/trainer/config_parser.py`中。全连接层python封装的例子中包含下面几步: + +- 所有的Python封装都使用:code:`@config_layer('fc')`这样的装饰器。网络层的标识符为:code:`fc`。 +- 实现构造函数:code:`__init__`。 + - 它首先调用基构造函数:code:`super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs)`。:code:`FCLayer`是Python封装的类名。:code:`fc`是网络层的标识符。为了封装能够正确工作,这些名字必须要写对。 + - 之后,计算转换矩阵的大小和格式(是否稀疏)。 + +.. code-block:: python + + @config_layer('fc') + class FCLayer(LayerBase): + def __init__( + self, + name, + size, + inputs, + bias=True, + **xargs): + super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs) + for input_index in xrange(len(self.inputs)): + input_layer = self.get_input_layer(input_index) + psize = self.config.size * input_layer.size + dims = [input_layer.size, self.config.size] + format = self.inputs[input_index].format + sparse = format == "csr" or format == "csc" + if sparse: + psize = self.inputs[input_index].nnz + self.create_input_parameter(input_index, psize, dims, sparse, format) + self.create_bias_parameter(bias, self.config.size) + +在网络配置中,网络层的细节可以通过下面这些代码片段来指定。这个类的参数包括: + +- :code:`name`是网络层实例的名字标识符。 +- :code:`type`是网络层的类型,通过网络层的标识符来指定。 +- :code:`size`是网络层输出的大小。 +- :code:`bias`表明这个层的一个实例是否需要偏置。 +- :code:`inputs`说明这个层的输入,输入是由一个list中的网络层实例的名字组成的。 + +.. code-block:: python + + Layer( + name = "fc1", + type = "fc", + size = 64, + bias = True, + inputs = [Input("pool3")] + ) + +我们建议你为你的Python封装实现一个“助手”,使得搭模型时更方便。具体可以参考:code:`python/paddle/trainer_config_helpers/layers.py`。 From 9284a6c1dc7234a6d4e18a4ebcecfaeee754800e Mon Sep 17 00:00:00 2001 From: Zhizhong Su Date: Wed, 28 Dec 2016 09:05:10 +0000 Subject: [PATCH 068/119] fix format problem in new_layer_cn.rst --- doc/howto/dev/new_layer_cn.rst | 116 ++++++++++++++++----------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/doc/howto/dev/new_layer_cn.rst b/doc/howto/dev/new_layer_cn.rst index ebec5fa206..8f5df0b36a 100644 --- a/doc/howto/dev/new_layer_cn.rst +++ b/doc/howto/dev/new_layer_cn.rst @@ -21,23 +21,23 @@ :scale: 60 % 一个网络层的前向传播部分把输入转化为相应的输出。 -全连接层以一个维度为:math:`D_i`稠密的向量作为输入。其用一个尺度为:math:`D_i \times D_o`的变换矩阵:math:`W`把:math:`x`映射到一个维度为:math:`D_o`的向量,并在其上再加上维度为:math:`D_o`的偏置向量:math:`b`。 +全连接层以一个维度为 :math:`D_i` 稠密的向量作为输入。其用一个尺度为 :math:`D_i \times D_o` 的变换矩阵 :math:`W` 把 :math:`x` 映射到一个维度为 :math:`D_o` 的向量,并在其上再加上维度为 :math:`D_o` 的偏置向量 :math:`b` 。 .. math:: y = f(W^T x + b) -其中:math:`f(.)`是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。 +其中 :math:`f(.)` 是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。 -变换矩阵:math:`W`和偏置向量:math:`b`是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传播对所有的参数和输入都计算输出函数的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 +变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传播对所有的参数和输入都计算输出函数的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 -假设我们的损失函数是:math:`c(y)`,那么 +假设我们的损失函数是 :math:`c(y)` ,那么 .. math:: \frac{\partial c(y)}{\partial x} = \frac{\partial c(y)}{\partial y} \frac{\partial y}{\partial x} -假设:math:`z = f(W^T x + b)`,那么 +假设 :math:`z = f(W^T x + b)` ,那么 .. math:: @@ -51,22 +51,22 @@ \frac{\partial z}{\partial x} = W, \frac{\partial z_j}{\partial W_{ij}} = x_i, \frac{\partial z}{\partial b} = \mathbf 1 -其中:math:`\mathbf 1`是一个全1的向量,:math:`W_{ij}`是矩阵:math:`W`第i行第j列的数值,:math:`z_j`是向量math:`z`的第j个值,:math:`x_i`是向量:math:`x`的第i个值。 +其中 :math:`\mathbf 1` 是一个全1的向量, :math:`W_{ij}` 是矩阵 :math:`W` 第i行第j列的数值, :math:`z_j` 是向量 :math:`z` 的第j个值, :math:`x_i` 是向量 :math:`x` 的第i个值。 -最后我们使用链式法则计算:math:`\frac{\partial z}{\partial x}`以及:math:`\frac{\partial z}{\partial W}`。计算的细节将在下面的小节给出。 +最后我们使用链式法则计算 :math:`\frac{\partial z}{\partial x}` 以及 :math:`\frac{\partial z}{\partial W}` 。计算的细节将在下面的小节给出。 实现C++类 =================== 一个网络层的C++类需要实现初始化,前向和后向。全连接层的实现位于:code:`paddle/gserver/layers/FullyConnectedLayer.h`及:code:`paddle/gserver/layers/FullyConnectedLayer.cpp`。这里我们展示一份简化过的代码。 -这个类需要继承:code:`paddle::Layer`这个基类,并且需要重写以下基类中的虚函数: +这个类需要继承 :code:`paddle::Layer` 这个基类,并且需要重写以下基类中的虚函数: - 类的构造函数和析构析构函数。 -- :code:`init`函数。用于初始化参数和设置。 -- :code:`forward`。实现网络层的前向传播。 -- :code:`backward`。实现网络层的后向传播。 -- :code:`prefetch`。用于确定由参数服务器预取的行相关的参数矩阵。如果该网络层不需要远程稀疏更新的话,你不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) +- :code:`init` 函数。用于初始化参数和设置。 +- :code:`forward` 。实现网络层的前向传播。 +- :code:`backward` 。实现网络层的后向传播。 +- :code:`prefetch` 。用于确定由参数服务器预取的行相关的参数矩阵。如果该网络层不需要远程稀疏更新的话,你不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) 头文件在下面列出: @@ -101,17 +101,17 @@ }; } // namespace paddle -头文件中把参数定位为类的成员变量。我们使用:code:`Weight`类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中由详细介绍。 +头文件中把参数定位为类的成员变量。我们使用 :code:`Weight` 类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中由详细介绍。 -- :code:`weights_`是存有变换矩阵的一系列权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 -- :code:`biases_`是存有偏置向量的权重。 +- :code:`weights_` 是存有变换矩阵的一系列权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 +- :code:`biases_` 是存有偏置向量的权重。 -全连接层没有网络层配置的超参数。如果一个网络层需要配置的话,通常的做法是将配置存于:code:`LayerConfig& config`中,并在类构建函数中把它放入一个类成员变量里。 +全连接层没有网络层配置的超参数。如果一个网络层需要配置的话,通常的做法是将配置存于 :code:`LayerConfig& config` 中,并在类构建函数中把它放入一个类成员变量里。 -下面的代码片段实现了:code:`init`函数。 +下面的代码片段实现了 :code:`init` 函数。 -- 首先,所有的:code:`init`函数必须先调用基类中的函数:code:`Layer::init(layerMap, parameterMap);`。该语句会为每个层初始化其所需要的变量和连接。 -- 之后初始化所有的权重矩阵:math:`W`。当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。 +- 首先,所有的 :code:`init` 函数必须先调用基类中的函数 :code:`Layer::init(layerMap, parameterMap);` 。该语句会为每个层初始化其所需要的变量和连接。 +- 之后初始化所有的权重矩阵 :math:`W` 。当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。 - 最后,初始化偏置向量。 @@ -151,10 +151,10 @@ 实现前向传播的部分有下面几个步骤。 -- 每个层在其:code:`forward`函数的开头必须调用:code:`Layer::forward(passType);`。 -- 之后使用:code:`reserveOutput(batchSize, size);`为输出分配内存。由于我们支持训练数据有不同的批次大小,所以这一步是必要的。:code:`reserveOutput` 会相应地改变输出的尺寸。为了保证效率,如果需要扩大矩阵,我们会重新分配内存;如果需要缩减矩阵,我们会继续使用现有的内存块。 -- 之后使用矩阵运算函数来计算:math:`\sum_i W_i x + b`。:code:`getInput(i).value`返回第i个输入矩阵。每个输入都是一个:math:`batchSize \times dim`的矩阵,每行表示一个批次中的单个输入。对于我们支持的全部矩阵操作,请参考:code:`paddle/math/Matrix.h`和:code:`paddle/math/BaseMatrix.h`。 -- 最终,使用:code:`forwardActivation();`进行激活操作。这会自动进行网络配置中声明的激活操作。 +- 每个层在其 :code:`forward` 函数的开头必须调用 :code:`Layer::forward(passType);` 。 +- 之后使用 :code:`reserveOutput(batchSize, size);` 为输出分配内存。由于我们支持训练数据有不同的批次大小,所以这一步是必要的。 :code:`reserveOutput` 会相应地改变输出的尺寸。为了保证效率,如果需要扩大矩阵,我们会重新分配内存;如果需要缩减矩阵,我们会继续使用现有的内存块。 +- 之后使用矩阵运算函数来计算 :math:`\sum_i W_i x + b`。:code:`getInput(i).value` 返回第i个输入矩阵。每个输入都是一个 :math:`batchSize \times dim` 的矩阵,每行表示一个批次中的单个输入。对于我们支持的全部矩阵操作,请参考 :code:`paddle/math/Matrix.h`和:code:`paddle/math/BaseMatrix.h` 。 +- 最终,使用 :code:`forwardActivation();` 进行激活操作。这会自动进行网络配置中声明的激活操作。 .. code-block:: c++ @@ -193,9 +193,9 @@ 实现后向传播的部分有下面几个步骤。 -- :code:`backwardActivation()`计算激活函数的梯度。梯度会就地(不使用额外空间)乘上输出的梯度,并可以通过:code:`getOutputGrad()`来获得。 -- 计算偏置的梯度。注意,我们使用:code:`biases_->getWGrad()`来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用:code:`getParameterPtr()->incUpdate(callback);`。这是用来在多线程和多机上更新参数的。 -- 之后,计算转换矩阵和输入的梯度,并对相应的参数调用:code:`incUpdate`。这给了框架一个机会去了解自己是否已经把所有的梯度收集到一个参数中,使得框架可以进行有时间重叠的工作。(例如,网络通信) +- :code:`backwardActivation()` 计算激活函数的梯度。梯度会就地(不使用额外空间)乘上输出的梯度,并可以通过 :code:`getOutputGrad()` 来获得。 +- 计算偏置的梯度。注意,我们使用 :code:`biases_->getWGrad()` 来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用 :code:`getParameterPtr()->incUpdate(callback);` 。这是用来在多线程和多机上更新参数的。 +- 之后,计算转换矩阵和输入的梯度,并对相应的参数调用 :code:`incUpdate` 。这给了框架一个机会去了解自己是否已经把所有的梯度收集到一个参数中,使得框架可以进行有时间重叠的工作。(例如,网络通信) .. code-block:: c++ @@ -238,7 +238,7 @@ } } -:code:`prefetch`函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。在远程稀疏训练时,完整的参数矩阵被分布式的保存在参数服务器上。当网络层用一个批次做训练时,该批次中,输入仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的转换矩阵的那些行。:code:`prefetch`表明了这些行的标号。 + :code:`prefetch` 函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。在远程稀疏训练时,完整的参数矩阵被分布式的保存在参数服务器上。当网络层用一个批次做训练时,该批次中,输入仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的转换矩阵的那些行。 :code:`prefetch` 表明了这些行的标号。 大多数层不需要远程稀疏训练函数。这种情况下不需要重写该函数。 @@ -255,7 +255,7 @@ } } -最后,使用:code:`REGISTER_LAYER(fc, FullyConnectedLayer);`来注册该层。:code:`fc`是该层的标识符,:code:`FullyConnectedLayer`是该层的类名。 +最后,使用 :code:`REGISTER_LAYER(fc, FullyConnectedLayer);` 来注册该层。 :code:`fc` 是该层的标识符, :code:`FullyConnectedLayer` 是该层的类名。 .. code-block:: c++ @@ -263,15 +263,15 @@ REGISTER_LAYER(fc, FullyConnectedLayer); } -若:code:`cpp`被放在:code:`paddle/gserver/layers`目录下,其会自动被加入编译列表。 +若 :code:`cpp` 被放在 :code:`paddle/gserver/layers` 目录下,其会自动被加入编译列表。 写梯度检查单元测试 =============================== -写梯度检查单元测试是一个验证新实现的层是否正确的相对简单的办法。梯度检查单元测试通过有限差分法来验证一个层的梯度。首先对输入做一个小的扰动:math:`\Delta x`,然后观察到输出的变化为:math:`\Delta y`,那么,梯度就可以通过这个方程计算得到:math:`\frac{\Delta y}{\Delta x }`。之后,再用这个梯度去和:code:`backward`函数得到的梯度去对比,以保证梯度计算的正确性。需要注意的是梯度检查仅仅验证了梯度的计算,并不保证:code:`forward`和:code:`backward`函数的实现是正确的。你需要一些更复杂的单元测试来保证你实现的网络层是正确的。 +写梯度检查单元测试是一个验证新实现的层是否正确的相对简单的办法。梯度检查单元测试通过有限差分法来验证一个层的梯度。首先对输入做一个小的扰动 :math:`\Delta x` ,然后观察到输出的变化为 :math:`\Delta y` ,那么,梯度就可以通过这个方程计算得到 :math:`\frac{\Delta y}{\Delta x }` 。之后,再用这个梯度去和 :code:`backward` 函数得到的梯度去对比,以保证梯度计算的正确性。需要注意的是梯度检查仅仅验证了梯度的计算,并不保证 :code:`forward` 和 :code:`backward` 函数的实现是正确的。你需要一些更复杂的单元测试来保证你实现的网络层是正确的。 -所有的梯度检测单侧都位于:code:`paddle/gserver/tests/test_LayerGrad.cpp`。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: +所有的梯度检测单侧都位于 :code:`paddle/gserver/tests/test_LayerGrad.cpp` 。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: + 生成网络层配置。网络层配置包含以下几项: - 偏置参数的大小。(例子中是4096) @@ -280,26 +280,26 @@ - 激活的类型。(例子中是softmax) - dropout的比例。(例子中是0.1) + 配置网络层的输入。在这个例子里,我们仅有一个输入。 - - 输入的类型(:code:`INPUT_DATA`),可以是以下几种: - - :code:`INPUT_DATA`:稠密向量。 - - :code:`INPUT_LABEL`:整数。 - - :code:`INPUT_DATA_TARGET`:稠密向量,但不用于计算梯度。 - - :code:`INPUT_SEQUENCE_DATA`:含有序列信息的稠密向量。 - - :code:`INPUT_HASSUB_SEQUENCE_DATA`:含有序列信息和子序列信息的稠密向量。 - - :code:`INPUT_SEQUENCE_LABEL`:含有序列信息的整数。 - - :code:`INPUT_SPARSE_NON_VALUE_DATA`:0-1稀疏数据。 - - :code:`INPUT_SPARSE_FLOAT_VALUE_DATA`:浮点稀疏数据。 - - 输入的名字。(例子中是:code:`layer_0`) + - 输入的类型( :code:`INPUT_DATA` ),可以是以下几种: + - :code:`INPUT_DATA` :稠密向量。 + - :code:`INPUT_LABEL` :整数。 + - :code:`INPUT_DATA_TARGET` :稠密向量,但不用于计算梯度。 + - :code:`INPUT_SEQUENCE_DATA` :含有序列信息的稠密向量。 + - :code:`INPUT_HASSUB_SEQUENCE_DATA` :含有序列信息和子序列信息的稠密向量。 + - :code:`INPUT_SEQUENCE_LABEL` :含有序列信息的整数。 + - :code:`INPUT_SPARSE_NON_VALUE_DATA` :0-1稀疏数据。 + - :code:`INPUT_SPARSE_FLOAT_VALUE_DATA` :浮点稀疏数据。 + - 输入的名字。(例子中是 :code:`layer_0` ) - 输入的大小。(例子中是8192) - 非零数字的个数,仅对稀疏数据有效。 - 稀疏数据的格式,仅对稀疏数据有效。 -+ 对每个输入,都需要调用一次:code:`config.layerConfig.add_inputs();`。 -+ 调用:code:`testLayerGrad`来做梯度检查。它包含下面的 It has the following arguments. - - 层和输入的配置。(例子中是:code:`config`) - - 输入的类型。(例子中是:code:`fc`) ++ 对每个输入,都需要调用一次 :code:`config.layerConfig.add_inputs();` 。 ++ 调用 :code:`testLayerGrad` 来做梯度检查。它包含下面的参数。 + - 层和输入的配置。(例子中是 :code:`config` ) + - 输入的类型。(例子中是 :code:`fc` ) - 梯度检查的批次大小。(例子中是100) - - 输入是否是转置的。大多数层需要设置为:code:`false`。(例子中是:code:`false`) - - 是否使用权重。有些层或者激活需要做归一化以保证它们的输出的和是一个常数。例如,softm激活的输出的和总是1。在这种情况下,我们不能通过常规的梯度检查的方式来计算梯度。因此我们采用输出的加权和(非常数)来计算梯度。(例子中是:code:`true`,因为全连接层的激活可以是softmax) + - 输入是否是转置的。大多数层需要设置为 :code:`false` 。(例子中是 :code:`false` ) + - 是否使用权重。有些层或者激活需要做归一化以保证它们的输出的和是一个常数。例如,softmax激活的输出的和总是1。在这种情况下,我们不能通过常规的梯度检查的方式来计算梯度。因此我们采用输出的加权和(非常数)来计算梯度。(例子中是 :code:`true` ,因为全连接层的激活可以是softmax) .. code-block:: c++ @@ -323,7 +323,7 @@ } } -如果你要为了测试而增加新的文件,例如:code:`paddle/gserver/tests/testFCGrad.cpp`,你需要把该文件加入:code:`paddle/gserver/tests/CMakeLists.txt`中。下面给出了一个例子。当你执行命令:code:`make tests`时,所有的单侧都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单侧正确执行。你需要在配置cmake时将:code:`WITH_DOUBLE`设置为`ON`。 +如果你要为了测试而增加新的文件,例如 :code:`paddle/gserver/tests/testFCGrad.cpp` ,你需要把该文件加入 :code:`paddle/gserver/tests/CMakeLists.txt` 中。下面给出了一个例子。当你执行命令 :code:`make tests` 时,所有的单侧都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单侧正确执行。你需要在配置cmake时将 :code:`WITH_DOUBLE` 设置为 `ON` 。 .. code-block:: bash @@ -339,11 +339,11 @@ 实现python封装 ======================== -python封装的实现使得我们可以在配置文件中使用新实现的网络层。所有的python封装都在:code:`python/paddle/trainer/config_parser.py`中。全连接层python封装的例子中包含下面几步: +python封装的实现使得我们可以在配置文件中使用新实现的网络层。所有的python封装都在 :code:`python/paddle/trainer/config_parser.py` 中。全连接层python封装的例子中包含下面几步: -- 所有的Python封装都使用:code:`@config_layer('fc')`这样的装饰器。网络层的标识符为:code:`fc`。 -- 实现构造函数:code:`__init__`。 - - 它首先调用基构造函数:code:`super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs)`。:code:`FCLayer`是Python封装的类名。:code:`fc`是网络层的标识符。为了封装能够正确工作,这些名字必须要写对。 +- 所有的Python封装都使用 :code:`@config_layer('fc')` 这样的装饰器。网络层的标识符为 :code:`fc` 。 +- 实现构造函数 :code:`__init__` 。 + - 它首先调用基构造函数 :code:`super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs)` 。 :code:`FCLayer` 是Python封装的类名。 :code:`fc` 是网络层的标识符。为了封装能够正确工作,这些名字必须要写对。 - 之后,计算转换矩阵的大小和格式(是否稀疏)。 .. code-block:: python @@ -371,11 +371,11 @@ python封装的实现使得我们可以在配置文件中使用新实现的网 在网络配置中,网络层的细节可以通过下面这些代码片段来指定。这个类的参数包括: -- :code:`name`是网络层实例的名字标识符。 -- :code:`type`是网络层的类型,通过网络层的标识符来指定。 -- :code:`size`是网络层输出的大小。 -- :code:`bias`表明这个层的一个实例是否需要偏置。 -- :code:`inputs`说明这个层的输入,输入是由一个list中的网络层实例的名字组成的。 +- :code:`name` 是网络层实例的名字标识符。 +- :code:`type` 是网络层的类型,通过网络层的标识符来指定。 +- :code:`size` 是网络层输出的大小。 +- :code:`bias` 表明这个层的一个实例是否需要偏置。 +- :code:`inputs` 说明这个层的输入,输入是由一个list中的网络层实例的名字组成的。 .. code-block:: python @@ -387,4 +387,4 @@ python封装的实现使得我们可以在配置文件中使用新实现的网 inputs = [Input("pool3")] ) -我们建议你为你的Python封装实现一个“助手”,使得搭模型时更方便。具体可以参考:code:`python/paddle/trainer_config_helpers/layers.py`。 +我们建议你为你的Python封装实现一个“助手”,使得搭模型时更方便。具体可以参考 :code:`python/paddle/trainer_config_helpers/layers.py` 。 From 918c1ed1c0674610920b4ce1ba32e5c1c17182a7 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 28 Dec 2016 19:20:09 +0800 Subject: [PATCH 069/119] move TestMain.cpp to testing --- paddle/CMakeLists.txt | 1 + paddle/function/CMakeLists.txt | 2 ++ paddle/testing/CMakeLists.txt | 6 ++++++ paddle/{utils => testing}/TestMain.cpp | 0 paddle/utils/CMakeLists.txt | 2 -- 5 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 paddle/testing/CMakeLists.txt rename paddle/{utils => testing}/TestMain.cpp (100%) diff --git a/paddle/CMakeLists.txt b/paddle/CMakeLists.txt index 2daea052b0..503024cff3 100644 --- a/paddle/CMakeLists.txt +++ b/paddle/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(cuda) add_subdirectory(function) add_subdirectory(utils) +add_subdirectory(testing) add_subdirectory(math) add_subdirectory(parameter) add_subdirectory(gserver) diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index c997448749..39bfa169d4 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -12,11 +12,13 @@ endif() add_library(paddle_function STATIC ${cpp_files} ${cu_objs}) if(WITH_GPU) +if(WITH_TESTING) # TODO: # file(GLOB test_files . *_op_test.cpp) # add_executable(${test_bin} EXCLUDE_FROM_ALL ${test_files}) add_simple_unittest(cross_map_normal_op_test) endif() +endif() add_style_check_target(paddle_function ${h_files}) add_style_check_target(paddle_function ${cpp_files}) diff --git a/paddle/testing/CMakeLists.txt b/paddle/testing/CMakeLists.txt new file mode 100644 index 0000000000..584498c860 --- /dev/null +++ b/paddle/testing/CMakeLists.txt @@ -0,0 +1,6 @@ +# for paddle test case + +if(WITH_TESTING) + add_library(paddle_test_main STATIC TestMain.cpp) + add_dependencies(paddle_test_main gen_proto_cpp) +endif() diff --git a/paddle/utils/TestMain.cpp b/paddle/testing/TestMain.cpp similarity index 100% rename from paddle/utils/TestMain.cpp rename to paddle/testing/TestMain.cpp diff --git a/paddle/utils/CMakeLists.txt b/paddle/utils/CMakeLists.txt index cf5a833014..45240b5002 100644 --- a/paddle/utils/CMakeLists.txt +++ b/paddle/utils/CMakeLists.txt @@ -5,8 +5,6 @@ file(GLOB UTIL_SOURCES . *.cpp) create_resources(enable_virtualenv.py enable_virtualenv.c) set(UTIL_RES enable_virtualenv.c) -add_library(paddle_test_main STATIC TestMain.cpp) - if(APPLE) file(GLOB UTIL_ARCH_SOURCES . arch/osx/*.cpp) else() From 6820b8f5f1660654010f9ac360b84ea97a7bad96 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 28 Dec 2016 19:39:37 +0800 Subject: [PATCH 070/119] fix error when WITH_TESTING=OFF --- cmake/util.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmake/util.cmake b/cmake/util.cmake index 8a71b23c62..0f60d9c6a3 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -107,7 +107,6 @@ function(link_paddle_exe TARGET_NAME) paddle_parameter paddle_proto paddle_cuda - paddle_test_main ${METRIC_LIBS} ${PROTOBUF_LIBRARY} ${LIBGLOG_LIBRARY} @@ -156,7 +155,7 @@ endfunction() function(link_paddle_test TARGET_NAME) link_paddle_exe(${TARGET_NAME}) target_link_libraries(${TARGET_NAME} ${GTEST_MAIN_LIBRARIES} - ${GTEST_LIBRARIES}) + ${GTEST_LIBRARIES} paddle_test_main) endfunction() # add_unittest_without_exec From c37c8a19b5c638036df54493702c281383e9e535 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 28 Dec 2016 20:27:04 +0800 Subject: [PATCH 071/119] remove gtest_main_library --- cmake/util.cmake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/util.cmake b/cmake/util.cmake index 0f60d9c6a3..0022e68be0 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -154,8 +154,9 @@ endfunction() # Rest Arguemnts: not used. function(link_paddle_test TARGET_NAME) link_paddle_exe(${TARGET_NAME}) - target_link_libraries(${TARGET_NAME} ${GTEST_MAIN_LIBRARIES} - ${GTEST_LIBRARIES} paddle_test_main) + target_link_libraries(${TARGET_NAME} + ${GTEST_LIBRARIES} + paddle_test_main) endfunction() # add_unittest_without_exec From 97f41534e03147fce81a934d06db2c78d7d972f7 Mon Sep 17 00:00:00 2001 From: hedaoyuan Date: Wed, 28 Dec 2016 20:56:48 +0800 Subject: [PATCH 072/119] fix link order error --- cmake/util.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/util.cmake b/cmake/util.cmake index 0022e68be0..43a56378df 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -155,8 +155,8 @@ endfunction() function(link_paddle_test TARGET_NAME) link_paddle_exe(${TARGET_NAME}) target_link_libraries(${TARGET_NAME} - ${GTEST_LIBRARIES} - paddle_test_main) + paddle_test_main + ${GTEST_LIBRARIES}) endfunction() # add_unittest_without_exec From 7e769ac063f766c9223e1293c3b28f56b669119c Mon Sep 17 00:00:00 2001 From: dayhaha <18800111918@163.com> Date: Thu, 29 Dec 2016 09:12:31 +0000 Subject: [PATCH 073/119] add python version evaluate function --- demo/recommendation/evaluate.py | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100755 demo/recommendation/evaluate.py diff --git a/demo/recommendation/evaluate.py b/demo/recommendation/evaluate.py new file mode 100755 index 0000000000..c96e1afc08 --- /dev/null +++ b/demo/recommendation/evaluate.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +import re +import math + + +def get_best_pass(filename): + with open(filename, 'r') as f: + text = f.read() + pattern = re.compile('Test.*? cost=([0-9]+\.[0-9]+).*?pass-([0-9]+)', + re.S) + results = re.findall(pattern, text) + sorted_results = sorted(results, key=lambda result: float(result[0])) + return sorted_results[0] + + +filename = sys.argv[1] +LOG = get_best_pass(filename) +predict_error = math.sqrt(float(LOG[0])) / 2 +print 'Best pass is %s, error is %s, which means predict get error as %f' % ( + LOG[1], LOG[0], predict_error) + +evaluate_pass = "output/pass-%s" % LOG[1] +print "evaluating from pass %s" % evaluate_pass From 381892bc976638ae9fdca53344a4dcbffd1650f2 Mon Sep 17 00:00:00 2001 From: dayhaha <18800111918@163.com> Date: Thu, 29 Dec 2016 09:19:42 +0000 Subject: [PATCH 074/119] change LOG to log --- demo/recommendation/evaluate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo/recommendation/evaluate.py b/demo/recommendation/evaluate.py index c96e1afc08..064c233b07 100755 --- a/demo/recommendation/evaluate.py +++ b/demo/recommendation/evaluate.py @@ -28,10 +28,10 @@ def get_best_pass(filename): filename = sys.argv[1] -LOG = get_best_pass(filename) -predict_error = math.sqrt(float(LOG[0])) / 2 +log = get_best_pass(filename) +predict_error = math.sqrt(float(log[0])) / 2 print 'Best pass is %s, error is %s, which means predict get error as %f' % ( - LOG[1], LOG[0], predict_error) + log[1], log[0], predict_error) -evaluate_pass = "output/pass-%s" % LOG[1] +evaluate_pass = "output/pass-%s" % log[1] print "evaluating from pass %s" % evaluate_pass From 3f3a9ff4189313fedcf276c25cab389f2499d42a Mon Sep 17 00:00:00 2001 From: dayhaha <18800111918@163.com> Date: Thu, 29 Dec 2016 13:15:29 +0000 Subject: [PATCH 075/119] modify follow comment --- demo/recommendation/evaluate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo/recommendation/evaluate.py b/demo/recommendation/evaluate.py index 064c233b07..3afa7a1e9d 100755 --- a/demo/recommendation/evaluate.py +++ b/demo/recommendation/evaluate.py @@ -17,8 +17,8 @@ import re import math -def get_best_pass(filename): - with open(filename, 'r') as f: +def get_best_pass(log_filename): + with open(log_filename, 'r') as f: text = f.read() pattern = re.compile('Test.*? cost=([0-9]+\.[0-9]+).*?pass-([0-9]+)', re.S) @@ -27,8 +27,8 @@ def get_best_pass(filename): return sorted_results[0] -filename = sys.argv[1] -log = get_best_pass(filename) +log_filename = sys.argv[1] +log = get_best_pass(log_filename) predict_error = math.sqrt(float(log[0])) / 2 print 'Best pass is %s, error is %s, which means predict get error as %f' % ( log[1], log[0], predict_error) From 838ef366dc6349ff25f88b6f69d2815c21ea8261 Mon Sep 17 00:00:00 2001 From: xutianbing Date: Sat, 24 Dec 2016 19:21:09 -0800 Subject: [PATCH 076/119] add first paddle function example for ContextProjectionForward operator, by going through Daoyuan's excellent paddle function design. --- paddle/function/CMakeLists.txt | 4 + paddle/function/Function.cpp | 28 ++++ paddle/function/Function.h | 2 + paddle/function/FunctionTest.h | 44 ++++-- paddle/function/context_projection_op.cpp | 136 +++++++++++++++++ paddle/function/context_projection_op.h | 43 ++++++ paddle/function/context_projection_op_gpu.cu | 137 ++++++++++++++++++ .../function/context_projection_op_test.cpp | 101 +++++++++++++ 8 files changed, 479 insertions(+), 16 deletions(-) create mode 100644 paddle/function/context_projection_op.cpp create mode 100644 paddle/function/context_projection_op.h create mode 100644 paddle/function/context_projection_op_gpu.cu create mode 100644 paddle/function/context_projection_op_test.cpp diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index b0e6f92f1e..f70ae9959b 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -17,6 +17,10 @@ if(WITH_TESTING) # file(GLOB test_files . *OpTest.cpp) # add_executable(${test_bin} EXCLUDE_FROM_ALL ${test_files}) add_simple_unittest(CrossMapNormalOpTest) + add_unittest(ContextProjectionOpTest + ContextProjectionOpTest.cpp + ContextProjectionOpGpu.cu + ../gserver/tests/TestUtil.cpp) endif() endif() diff --git a/paddle/function/Function.cpp b/paddle/function/Function.cpp index eb005e6744..215b3dbd83 100644 --- a/paddle/function/Function.cpp +++ b/paddle/function/Function.cpp @@ -30,6 +30,20 @@ real FuncConfig::get(const std::string& key) const { return it->second.r; } +template <> +int FuncConfig::get(const std::string& key) const { + auto it = valueMap_.find(key); + CHECK(it != valueMap_.end()) << "Cannot find value: '" << key << "'"; + return it->second.i; +} + +template <> +bool FuncConfig::get(const std::string& key) const { + auto it = valueMap_.find(key); + CHECK(it != valueMap_.end()) << "Cannot find value: '" << key << "'"; + return it->second.b; +} + template <> FuncConfig& FuncConfig::set(const std::string& key, size_t v) { CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; @@ -44,6 +58,20 @@ FuncConfig& FuncConfig::set(const std::string& key, real v) { return *this; } +template <> +FuncConfig& FuncConfig::set(const std::string& key, int v) { + CHECK(valueMap_.count(key) == 0) << "Duplicated value: " << key; + valueMap_[key].i = v; + return *this; +} + +template <> +FuncConfig& FuncConfig::set(const std::string& key, bool v) { + CHECK(valueMap_.count(key) == 0) << "Duplicated value: " << key; + valueMap_[key].b = v; + return *this; +} + ClassRegistrar FunctionBase::funcRegistrar_; } // namespace paddle diff --git a/paddle/function/Function.h b/paddle/function/Function.h index 095584c0b1..210eba1301 100644 --- a/paddle/function/Function.h +++ b/paddle/function/Function.h @@ -59,6 +59,8 @@ public: union value { size_t s; real r; + int i; + bool b; }; template diff --git a/paddle/function/FunctionTest.h b/paddle/function/FunctionTest.h index a602bde57e..32131037f6 100644 --- a/paddle/function/FunctionTest.h +++ b/paddle/function/FunctionTest.h @@ -33,25 +33,33 @@ public: // init cpu and gpu arguments auto initArgs = [=]( Arguments& cpuArgs, Arguments& gpuArgs, const Arguments& inArgs) { - for (auto arg : inArgs) { + for (const auto arg : inArgs) { size_t size = sizeof(real); - for (auto dim : arg.dims_) { + for (const auto dim : arg.dims_) { size *= dim; } - cpuMemory.emplace_back(std::make_shared(size)); - gpuMemory.emplace_back(std::make_shared(size)); - cpuArgs.emplace_back( - Tensor((real*)cpuMemory.back()->getBuf(), arg.dims_)); - gpuArgs.emplace_back( - Tensor((real*)gpuMemory.back()->getBuf(), arg.dims_)); - - // will use an api to refactor this code. - CpuVector cpuVector(size / sizeof(real), - (real*)cpuArgs.back().getData()); - GpuVector gpuVector(size / sizeof(real), - (real*)gpuArgs.back().getData()); - cpuVector.uniform(0.001, 1); - gpuVector.copyFrom(cpuVector); + if (arg.getData()) { + // todo(tianbing), waste unnecessary mem here + cpuMemory.emplace_back(std::make_shared(size)); + gpuMemory.emplace_back(std::make_shared(size)); + cpuArgs.emplace_back(Tensor((real*)arg.getData(), arg.dims_)); + gpuArgs.emplace_back(Tensor((real*)arg.getData(), arg.dims_)); + // already init outside + } else { + cpuMemory.emplace_back(std::make_shared(size)); + gpuMemory.emplace_back(std::make_shared(size)); + cpuArgs.emplace_back( + Tensor((real*)cpuMemory.back()->getBuf(), arg.dims_)); + gpuArgs.emplace_back( + Tensor((real*)gpuMemory.back()->getBuf(), arg.dims_)); + // will use an api to refactor this code. + CpuVector cpuVector(size / sizeof(real), + (real*)cpuArgs.back().getData()); + GpuVector gpuVector(size / sizeof(real), + (real*)gpuArgs.back().getData()); + cpuVector.uniform(0.001, 1); + gpuVector.copyFrom(cpuVector); + } } }; initArgs(cpuInputs, gpuInputs, inputs); @@ -81,6 +89,10 @@ public: checkArgs(cpuInouts, gpuInouts); } + std::shared_ptr getCpuFunction() const { return cpu; } + + std::shared_ptr getGpuFunction() const { return gpu; } + protected: std::shared_ptr cpu; std::shared_ptr gpu; diff --git a/paddle/function/context_projection_op.cpp b/paddle/function/context_projection_op.cpp new file mode 100644 index 0000000000..75c41eed14 --- /dev/null +++ b/paddle/function/context_projection_op.cpp @@ -0,0 +1,136 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include "context_projection_op.h" +#include "paddle/math/Matrix.h" +#include "paddle/math/Vector.h" + +namespace paddle { + +template <> +void ContextProjectionForward(Tensor& output, + const Tensor& input, + const Tensor& weight, + const Tensor& sequence, + size_t context_length, + int context_start, + size_t begin_pad, + bool is_padding) { + CHECK(output.getData() && input.getData() && sequence.getData()); + CHECK_EQ(output.dims_.size(), 2); + CHECK_EQ(input.dims_.size(), 2); + CHECK_EQ(weight.dims_.size(), 2); + CHECK_EQ(sequence.dims_.size(), 1); + + auto out_mat = std::make_shared( + output.getData(), output.dims_[0], output.dims_[1]); + const auto in_mat = std::make_shared( + input.getData(), input.dims_[0], input.dims_[1]); + const auto weight_mat = + !weight.getData() + ? nullptr + : std::make_shared( + weight.getData(), weight.dims_[0], input.dims_[1]); + CpuIVector seq_vec(sequence.dims_[0], + reinterpret_cast(sequence.getData())); + CHECK_EQ(out_mat->getWidth(), in_mat->getWidth() * context_length); + + const int* starts = seq_vec.getData(); + const size_t num_sequences = seq_vec.getSize() - 1; + for (size_t i = 0; i < num_sequences; ++i) { + for (size_t j = 0; j < context_length; ++j) { + int begin = starts[i] + context_start + j; + int end = starts[i + 1] + context_start + j; + int dst_begin = starts[i]; + int dst_end = starts[i + 1]; + if (begin < starts[i]) { + int64_t pad_size = + std::min(starts[i] - begin, starts[i + 1] - starts[i]); + MatrixPtr mat = out_mat->subMatrix(starts[i], pad_size); + if (is_padding && weight_mat) { + MatrixPtr sub = weight_mat->subMatrix(j, pad_size); + mat->addAtOffset(*sub, j * in_mat->getWidth()); + } + dst_begin = starts[i] + pad_size; + begin = starts[i]; + } + if (end > starts[i + 1]) { + int64_t pad_size = + std::min(end - starts[i + 1], starts[i + 1] - starts[i]); + MatrixPtr mat = out_mat->subMatrix(starts[i + 1] - pad_size, pad_size); + if (is_padding && weight_mat) { + MatrixPtr sub = weight_mat->subMatrix( + begin_pad + context_start + j - pad_size, pad_size); + mat->addAtOffset(*sub, j * in_mat->getWidth()); + } + dst_end = starts[i + 1] - pad_size; + end = starts[i + 1]; + } + if (end <= begin) continue; + MatrixPtr src = in_mat->subMatrix(begin, end - begin); + MatrixPtr dst = out_mat->subMatrix(dst_begin, dst_end - dst_begin); + dst->addAtOffset(*src, j * in_mat->getWidth()); + } + } +} + +/** + * \param inputs[0] input value. + * \param inputs[1] input weight. + * \param inputs[2] input sequence. + * \param outputs[0] output value. + */ +template +class ContextProjectionForwardFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override { + context_length_ = config.get("context_length"); + context_start_ = config.get("context_start"); + begin_pad_ = config.get("begin_pad"); + is_padding_ = config.get("is_padding"); + } + + void calc(const Arguments& inputs, + const Arguments& outputs, + const Arguments& inouts) override { + CHECK_EQ(3, inputs.size()); + CHECK_EQ(1, outputs.size()); + CHECK_EQ(0, inouts.size()); + + ContextProjectionForward((Tensor&)outputs[0], + inputs[0], + inputs[1], + inputs[2], + context_length_, + context_start_, + begin_pad_, + is_padding_); + } + +private: + size_t context_length_; + int context_start_; + size_t begin_pad_; + bool is_padding_; +}; + +REGISTER_TYPED_FUNC(ContextProjectionForward, + CPU, + ContextProjectionForwardFunc); +#ifndef PADDLE_ONLY_CPU +REGISTER_TYPED_FUNC(ContextProjectionForward, + GPU, + ContextProjectionForwardFunc); +#endif +} // namespace paddle diff --git a/paddle/function/context_projection_op.h b/paddle/function/context_projection_op.h new file mode 100644 index 0000000000..bdc5071bc6 --- /dev/null +++ b/paddle/function/context_projection_op.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + +#include "Function.h" + +namespace paddle { + +/** + * \brief Context Projection Forward. + * + * \param[out] outputs output data. + * \param[in] input input data. + * \param[in] weight input weight. + * \param[in] sequence input data. + * \param[in] context_length consecutive rows for concatenation. + * \param[in] begin_pad context start position. + * \param[in] is_padding whether padding 0 or not. + * + */ +template +void ContextProjectionForward(Tensor& output, + const Tensor& input, + const Tensor& weight, + const Tensor& sequence, + size_t context_length, + int context_start, + size_t begin_pad, + bool is_padding); + +} // namespace paddle diff --git a/paddle/function/context_projection_op_gpu.cu b/paddle/function/context_projection_op_gpu.cu new file mode 100644 index 0000000000..4e7958164b --- /dev/null +++ b/paddle/function/context_projection_op_gpu.cu @@ -0,0 +1,137 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include "hl_base.h" +#include "context_projection_op.h" + +namespace paddle { + +template +__global__ void KeContextProjectionForward(const real* input, + const int* sequence, + const real* weight, + real* output, + int input_dim, + int context_length, + int context_start, + int begin_pad) { + int idx = threadIdx.x; + int block_size = blockDim.x; + int sequenceId = blockIdx.x; + int seq_start = sequence[sequenceId]; + int seq_end = sequence[sequenceId+1]; + real value = 0; + + int instances = seq_end - seq_start + context_length - 1; + output += seq_start * input_dim * context_length; + input += seq_start * input_dim; + for (int k = 0; k <= input_dim / block_size; k++) { + if (idx < input_dim) { + for (int i = 0; i < instances; i++) { + // i + context_start; + if ((i + context_start) < 0) { + if (padding) { + value = weight[i * input_dim + idx]; + } else { + continue; + } + } else if ((i + context_start) >= (seq_end - seq_start)) { + if (padding) { + value = + weight[(begin_pad + i + context_start - (seq_end - seq_start)) * + input_dim + idx]; + } else { + continue; + } + } else { + value = input[(i + context_start) * input_dim + idx]; + } + + int outx = (i - context_length) < 0 ? i : (context_length - 1); + int outy = (i - context_length) < 0 ? 0 : (i - (context_length - 1)); + real* output_r = + output + outy * input_dim * context_length + outx * input_dim; + for (int j = outy; j < seq_end - seq_start; j++) { + output_r[idx] += value; + if (j - outy == outx) break; + output_r += (context_length - 1) * input_dim; + } + } + } + idx += block_size; + } +} + +void hl_context_projection_forward(const real* input, + const int* sequence, + real* weight, + real* output, + int num_sequences, + int input_dim, + int context_length, + int context_start, + int begin_pad, + bool is_padding) { + CHECK_NOTNULL(input); + CHECK_NOTNULL(sequence); + CHECK_NOTNULL(output); + CHECK(!is_padding || weight); + + int block_size = 128; + int blocks_x = num_sequences; + int blocks_y = 1; + dim3 threads(block_size, 1); + dim3 grid(blocks_x, blocks_y); + + if (is_padding) { + KeContextProjectionForward<<< grid, threads, 0, STREAM_DEFAULT >>> + (input, sequence, weight, output, input_dim, + context_length, context_start, begin_pad); + } else { + KeContextProjectionForward<<< grid, threads, 0, STREAM_DEFAULT >>> + (input, sequence, weight, output, input_dim, + context_length, context_start, begin_pad); + } + CHECK_SYNC("hl_context_projection_forward failed"); +} + +template <> +void ContextProjectionForward(Tensor& output, + const Tensor& input, + const Tensor& weight, + const Tensor& sequence, + size_t context_length, + int context_start, + size_t begin_pad, + bool is_padding) { + CHECK(output.getData() && input.getData() && sequence.getData()); + CHECK_EQ(output.dims_.size(), 2); + CHECK_EQ(input.dims_.size(), 2); + CHECK_EQ(weight.dims_.size(), 2); + CHECK_EQ(sequence.dims_.size(), 1); + CHECK_EQ(output.dims_[1], input.dims_[1] * context_length); + + hl_context_projection_forward(input.getData(), + reinterpret_cast(sequence.getData()), + weight.getData(), + output.getData(), + sequence.dims_[0] - 1, + input.dims_[1], + context_length, + context_start, + begin_pad, + is_padding); +} + +} // namespace paddle diff --git a/paddle/function/context_projection_op_test.cpp b/paddle/function/context_projection_op_test.cpp new file mode 100644 index 0000000000..98784471ae --- /dev/null +++ b/paddle/function/context_projection_op_test.cpp @@ -0,0 +1,101 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include +#include "FunctionTest.h" +#include "paddle/gserver/tests/TestUtil.h" +#include "paddle/math/Matrix.h" + +using namespace paddle; // NOLINT + +void testMatrixProjectionForward(int context_start, + size_t context_length, + bool is_padding, + size_t batch_size, + size_t input_dim) { + size_t pad = std::max(0, -context_start) + + std::max(0, (int)(context_start + context_length - 1)); + if (pad == 0) is_padding = false; + + FunctionCompare compare("ContextProjectionForward", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", std::max(0, -context_start)) + .set("is_padding", is_padding)); + + CpuMatrix cpu_in(batch_size, input_dim); + cpu_in.randomizeUniform(); + GpuMatrix gpu_in(batch_size, input_dim); + gpu_in.copyFrom(cpu_in); + auto cpu_weight = + is_padding ? std::make_shared(pad, input_dim) : nullptr; + auto gpu_weight = + is_padding ? std::make_shared(pad, input_dim) : nullptr; + if (is_padding) { + cpu_weight->randomizeUniform(); + gpu_weight->copyFrom(*cpu_weight); + } + IVectorPtr cpu_seq; + generateSequenceStartPositions(batch_size, cpu_seq); + IVectorPtr gpu_seq = IVector::create(cpu_seq->getSize(), true); + gpu_seq->copyFrom(*cpu_seq); + + CpuMatrix cpu_out(batch_size, input_dim * context_length); + GpuMatrix gpu_out(batch_size, input_dim * context_length); + cpu_out.randomizeUniform(); + gpu_out.copyFrom(cpu_out); + + compare.getCpuFunction()->calc( + {Tensor(cpu_in.getData(), Dims{batch_size, input_dim}), + Tensor(cpu_weight ? cpu_weight->getData() : nullptr, + Dims{pad, input_dim}), + Tensor(reinterpret_cast(cpu_seq->getData()), + Dims{cpu_seq->getSize()})}, + {Tensor(cpu_out.getData(), Dims{batch_size, input_dim * context_length})}, + {}); + compare.getGpuFunction()->calc( + {Tensor(gpu_in.getData(), Dims{batch_size, input_dim}), + Tensor(gpu_weight ? gpu_weight->getData() : nullptr, + Dims{pad, input_dim}), + Tensor(reinterpret_cast(gpu_seq->getData()), + Dims{gpu_seq->getSize()})}, + {Tensor(gpu_out.getData(), Dims{batch_size, input_dim * context_length})}, + {}); + + autotest::TensorCheckEqual(cpu_out, gpu_out); +} + +TEST(ContextProjectionForward, projection) { + for (auto context_start : {-5, -3, -1, 0, 3}) { + for (auto context_length : {1, 2, 5, 7}) { + for (auto trainable_padding : {false, true}) { + for (auto batch_size : {1, 2, 5, 20, 100}) { + for (auto input_dim : {15, 32, 63, 128, 200}) { + VLOG(3) << " context_start=" << context_start + << " context_length=" << context_length + << " trainable_padding=" << trainable_padding + << " batch_size=" << batch_size + << " input_dim=" << input_dim; + testMatrixProjectionForward(context_start, + context_length, + trainable_padding, + batch_size, + input_dim); + } + } + } + } + } +} From 590ecba30576f166579ac49e4fa042af9e191fd1 Mon Sep 17 00:00:00 2001 From: xutianbing Date: Tue, 27 Dec 2016 15:40:35 -0800 Subject: [PATCH 077/119] add ContextProjectionBackward, ContextProjectionBackwardData, ContextProjectionBackwardWeightw --- paddle/function/CMakeLists.txt | 1 - paddle/function/context_projection_op.cpp | 197 +++++++++++++++- paddle/function/context_projection_op.h | 46 +++- paddle/function/context_projection_op_gpu.cu | 210 ++++++++++++++++++ .../function/context_projection_op_test.cpp | 100 ++++++++- 5 files changed, 548 insertions(+), 6 deletions(-) diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index f70ae9959b..0b3126155d 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -19,7 +19,6 @@ if(WITH_TESTING) add_simple_unittest(CrossMapNormalOpTest) add_unittest(ContextProjectionOpTest ContextProjectionOpTest.cpp - ContextProjectionOpGpu.cu ../gserver/tests/TestUtil.cpp) endif() endif() diff --git a/paddle/function/context_projection_op.cpp b/paddle/function/context_projection_op.cpp index 75c41eed14..a6a85fb6a4 100644 --- a/paddle/function/context_projection_op.cpp +++ b/paddle/function/context_projection_op.cpp @@ -41,7 +41,7 @@ void ContextProjectionForward(Tensor& output, !weight.getData() ? nullptr : std::make_shared( - weight.getData(), weight.dims_[0], input.dims_[1]); + weight.getData(), weight.dims_[0], weight.dims_[1]); CpuIVector seq_vec(sequence.dims_[0], reinterpret_cast(sequence.getData())); CHECK_EQ(out_mat->getWidth(), in_mat->getWidth() * context_length); @@ -125,12 +125,207 @@ private: bool is_padding_; }; +template <> +void ContextProjectionBackward(Tensor& out_grad, + const Tensor& in_grad, + const Tensor& w_grad, + const Tensor& sequence, + size_t context_length, + int context_start, + size_t begin_pad, + bool is_padding) { + CHECK(out_grad.getData() && sequence.getData()); + CHECK_EQ(out_grad.dims_.size(), 2); + CHECK_EQ(in_grad.dims_.size(), 2); + CHECK_EQ(w_grad.dims_.size(), 2); + CHECK_EQ(sequence.dims_.size(), 1); + + auto out_grad_mat = std::make_shared( + out_grad.getData(), out_grad.dims_[0], out_grad.dims_[1]); + const auto in_grad_mat = + !in_grad.getData() + ? nullptr + : std::make_shared( + in_grad.getData(), in_grad.dims_[0], in_grad.dims_[1]); + const auto w_grad_mat = + !w_grad.getData() + ? nullptr + : std::make_shared( + w_grad.getData(), w_grad.dims_[0], w_grad.dims_[1]); + CpuIVector seq_vec(sequence.dims_[0], + reinterpret_cast(sequence.getData())); + CHECK_EQ(out_grad_mat->getWidth(), in_grad_mat->getWidth() * context_length); + + size_t input_dim = in_grad_mat ? in_grad_mat->getWidth() + : w_grad_mat ? w_grad_mat->getWidth() : 0; + CHECK_EQ(out_grad_mat->getWidth(), input_dim * context_length); + + const int* starts = seq_vec.getData(); + size_t num_sequences = seq_vec.getSize() - 1; + for (size_t i = 0; i < num_sequences; ++i) { + for (size_t j = 0; j < context_length; ++j) { + int begin = starts[i] + context_start + j; + int end = starts[i + 1] + context_start + j; + int dst_begin = starts[i]; + int dst_end = starts[i + 1]; + if (begin < starts[i]) { + int64_t pad_size = + std::min(starts[i] - begin, starts[i + 1] - starts[i]); + if (is_padding && w_grad_mat) { + MatrixPtr mat = out_grad_mat->subMatrix(starts[i], pad_size); + MatrixPtr sub = w_grad_mat->subMatrix(j, pad_size); + sub->addAtOffset(*mat, j * input_dim); + } + dst_begin = starts[i] + pad_size; + begin = starts[i]; + } + if (end > starts[i + 1]) { + int64_t pad_size = + std::min(end - starts[i + 1], starts[i + 1] - starts[i]); + if (is_padding && w_grad_mat) { + MatrixPtr mat = + out_grad_mat->subMatrix(starts[i + 1] - pad_size, pad_size); + MatrixPtr sub = w_grad_mat->subMatrix( + begin_pad + context_start + j - pad_size, pad_size); + sub->addAtOffset(*mat, j * input_dim); + } + dst_end = starts[i + 1] - pad_size; + end = starts[i + 1]; + } + if (end <= begin) continue; + if (!in_grad_mat) continue; + MatrixPtr src = in_grad_mat->subMatrix(begin, end - begin); + MatrixPtr dst = out_grad_mat->subMatrix(dst_begin, dst_end - dst_begin); + src->addAtOffset(*dst, j * input_dim); + } + } +} + +/** + * \param inputs[0] input value. + * \param inputs[1] input weight. + * \param inputs[2] input sequence. + * \param outputs[0] output value. + */ +template +class ContextProjectionBackwardFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override { + context_length_ = config.get("context_length"); + context_start_ = config.get("context_start"); + begin_pad_ = config.get("begin_pad"); + is_padding_ = config.get("is_padding"); + } + + void calc(const Arguments& inputs, + const Arguments& outputs, + const Arguments& inouts) override { + CHECK_EQ(3, inputs.size()); + CHECK_EQ(1, outputs.size()); + CHECK_EQ(0, inouts.size()); + + ContextProjectionBackward((Tensor&)outputs[0], + inputs[0], + inputs[1], + inputs[2], + context_length_, + context_start_, + begin_pad_, + is_padding_); + } + +private: + size_t context_length_; + int context_start_; + size_t begin_pad_; + bool is_padding_; +}; + +/** + * \param inputs[0] input grad. + * \param inputs[1] input sequence. + * \param outputs[0] output grad. + */ +template +class ContextProjectionBackwardDataFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override { + context_length_ = config.get("context_length"); + context_start_ = config.get("context_start"); + } + + void calc(const Arguments& inputs, + const Arguments& outputs, + const Arguments& inouts) override { + CHECK_EQ(2, inputs.size()); + CHECK_EQ(1, outputs.size()); + CHECK_EQ(0, inouts.size()); + + ContextProjectionBackwardData((Tensor&)outputs[0], + (Tensor&)inputs[0], + inputs[1], + context_length_, + context_start_); + } + +private: + size_t context_length_; + int context_start_; +}; + +/** + * \param inputs[0] weight grad. + * \param inputs[1] input sequence. + * \param outputs[0] output grad. + */ +template +class ContextProjectionBackwardWeightFunc : public FunctionBase { +public: + void init(const FuncConfig& config) override { + context_length_ = config.get("context_length"); + context_start_ = config.get("context_start"); + begin_pad_ = config.get("begin_pad"); + total_pad_ = config.get("total_pad"); + } + + void calc(const Arguments& inputs, + const Arguments& outputs, + const Arguments& inouts) override { + CHECK_EQ(2, inputs.size()); + CHECK_EQ(1, outputs.size()); + CHECK_EQ(0, inouts.size()); + + ContextProjectionBackwardWeight((Tensor&)outputs[0], + (Tensor&)inputs[0], + inputs[1], + context_length_, + context_start_, + total_pad_, + begin_pad_); + } + +private: + size_t context_length_; + int context_start_; + size_t begin_pad_; + size_t total_pad_; +}; + REGISTER_TYPED_FUNC(ContextProjectionForward, CPU, ContextProjectionForwardFunc); +REGISTER_TYPED_FUNC(ContextProjectionBackward, + CPU, + ContextProjectionBackwardFunc); #ifndef PADDLE_ONLY_CPU REGISTER_TYPED_FUNC(ContextProjectionForward, GPU, ContextProjectionForwardFunc); +REGISTER_TYPED_FUNC(ContextProjectionBackwardData, + GPU, + ContextProjectionBackwardDataFunc); +REGISTER_TYPED_FUNC(ContextProjectionBackwardWeight, + GPU, + ContextProjectionBackwardWeightFunc); #endif } // namespace paddle diff --git a/paddle/function/context_projection_op.h b/paddle/function/context_projection_op.h index bdc5071bc6..5f4e0761db 100644 --- a/paddle/function/context_projection_op.h +++ b/paddle/function/context_projection_op.h @@ -25,9 +25,10 @@ namespace paddle { * \param[in] input input data. * \param[in] weight input weight. * \param[in] sequence input data. - * \param[in] context_length consecutive rows for concatenation. - * \param[in] begin_pad context start position. - * \param[in] is_padding whether padding 0 or not. + * \param[in] context_length consecutive rows for concatenation. + * \param[in] context_start context start position. + * \param[in] begin_pad begining pad position. + * \param[in] is_padding whether padding 0 or not. * */ template @@ -40,4 +41,43 @@ void ContextProjectionForward(Tensor& output, size_t begin_pad, bool is_padding); +/** + * \brief Context Projection Backward. + * + * \param[out] outputs output gradient. + * \param[in] input input gradient. + * \param[in] weight input weight gradient. + * \param[in] sequence input data. + * \param[in] context_length consecutive rows for concatenation. + * \param[in] context_start context start position. + * \param[in] begin_pad begining pad position. + * \param[in] is_padding whether padding 0 or not. + * + */ +template +void ContextProjectionBackward(Tensor& out_grad, + const Tensor& in_grad, + const Tensor& w_grad, + const Tensor& sequence, + size_t context_length, + int context_start, + size_t begin_pad, + bool is_padding); + +template +void ContextProjectionBackwardData(Tensor& out_grad, + Tensor& in_grad, + const Tensor& sequence, + size_t context_length, + int context_start); + +template +void ContextProjectionBackwardWeight(Tensor& out_grad, + Tensor& w_grad, + const Tensor& sequence, + size_t context_length, + int context_start, + size_t total_pad, + size_t begin_pad); + } // namespace paddle diff --git a/paddle/function/context_projection_op_gpu.cu b/paddle/function/context_projection_op_gpu.cu index 4e7958164b..fdea433d07 100644 --- a/paddle/function/context_projection_op_gpu.cu +++ b/paddle/function/context_projection_op_gpu.cu @@ -134,4 +134,214 @@ void ContextProjectionForward(Tensor& output, is_padding); } +__global__ void KeContextProjectionBackwardData(real* out_grad, + const int* sequence, + real* in_grad, + int input_dim, + int context_length, + int context_start) { + int idx = threadIdx.x; + int block_size = blockDim.x; + int sequenceId = blockIdx.x; + int seq_start = sequence[sequenceId]; + int seq_end = sequence[sequenceId+1]; + real value = 0; + + int instances = seq_end - seq_start + context_length - 1; + out_grad += seq_start * input_dim * context_length; + in_grad += seq_start * input_dim; + for (int k = 0; k <= input_dim / block_size; k++) { + if (idx < input_dim) { + for (int i = 0; i < instances; i++) { + if ((i + context_start) < 0) { + continue; + } else if ((i + context_start) >= (seq_end - seq_start)) { + continue; + } else { + // value = 0; + value = in_grad[(i + context_start) * input_dim + idx]; + } + + int outx = (i - context_length) < 0 ? i : (context_length - 1); + int outy = (i - context_length) < 0 ? 0 : (i - (context_length - 1)); + real* output_r = + out_grad + outy * input_dim * context_length + outx * input_dim; + for (int j = outy; j < seq_end - seq_start; j++) { + value += output_r[idx]; + if (j - outy == outx) break; + output_r += (context_length - 1) * input_dim; + } + in_grad[(i + context_start) * input_dim + idx] = value; + } + } + idx += block_size; + } +} + +void hl_context_projection_backward_data(real* out_grad, + const int* sequence, + real* input_grad, + int num_sequences, + int input_dim, + int context_length, + int context_start) { + CHECK_NOTNULL(out_grad); + CHECK_NOTNULL(sequence); + CHECK_NOTNULL(input_grad); + + int block_size = 128; + int blocks_x = num_sequences; + int blocks_y = 1; + dim3 threads(block_size, 1); + dim3 grid(blocks_x, blocks_y); + KeContextProjectionBackwardData<<< grid, threads, 0, STREAM_DEFAULT >>> + (out_grad, sequence, input_grad, input_dim, context_length, context_start); + CHECK_SYNC("hl_context_projection_backward_data failed"); +} + +template <> +void ContextProjectionBackwardData(Tensor& out_grad, + Tensor& in_grad, + const Tensor& sequence, + size_t context_length, + int context_start) { + CHECK(in_grad.getData() && out_grad.getData() && sequence.getData()); + CHECK_EQ(out_grad.dims_.size(), 2); + CHECK_EQ(in_grad.dims_.size(), 2); + CHECK_EQ(sequence.dims_.size(), 1); + CHECK_EQ(out_grad.dims_[1], in_grad.dims_[1] * context_length); + + hl_context_projection_backward_data(out_grad.getData(), + reinterpret_cast(sequence.getData()), + in_grad.getData(), + sequence.dims_[0] - 1, + in_grad.dims_[1], + context_length, + context_start); +} + +template +__global__ void KeContextProjectionBackwardWeight(real* out_grad, + const int* sequence, + real* w_grad, + int num_sequences, + int w_dim, + int context_length, + int context_start, + int begin_pad) { + __shared__ real sum_s[THREADS_Y][THREADS_X]; + int pad_of_block = (w_dim + THREADS_X - 1) / THREADS_X; + const int idx = threadIdx.x; + const int idy = threadIdx.y; + int padId = blockIdx.x / pad_of_block; + int weight_idx = idx + THREADS_X * (blockIdx.x % pad_of_block); + int instanceId; + real value = 0; + real* output_r; + + sum_s[idy][idx] = 0.0f; + if (weight_idx < w_dim) { + for (int seqId = idy; seqId < num_sequences; seqId += THREADS_Y) { + int seq_start = sequence[seqId]; + int seq_end = sequence[seqId+1]; + output_r = out_grad + seq_start * w_dim * context_length; + + if (context_start < 0) { + if (padId + context_start < 0) { + instanceId = padId; + } else { + // begin_pad > 0; + instanceId = (padId - begin_pad) + + (seq_end - seq_start) - context_start; + } + } else { + if (padId + (seq_end - seq_start) < context_start) { + continue; + } else { + // begin_pad == 0; + instanceId = padId + (seq_end - seq_start) - context_start; + } + } + + int outx = (instanceId - context_length) < 0 ? + instanceId : (context_length - 1); + int outy = (instanceId - context_length) < 0 ? + 0 : (instanceId - (context_length - 1)); + output_r += outy * w_dim * context_length + outx * w_dim; + for (int j = outy; j < seq_end - seq_start; j++) { + value += output_r[weight_idx]; + if (j - outy == outx) break; + output_r += (context_length - 1) * w_dim; + } + } + sum_s[idy][idx] = value; + } + __syncthreads(); + + for (int stride = THREADS_Y/2; stride > 0; stride = stride/2) { + if (idy < stride) { + sum_s[idy][idx] += sum_s[idy + stride][idx]; + } + __syncthreads(); + } + __syncthreads(); + + if (weight_idx < w_dim) { + if (idy == 0) { + w_grad[padId * w_dim + weight_idx] += sum_s[0][idx]; + } + } +} + +void hl_context_projection_backward_weight(real* out_grad, + const int* sequence, + real* w_grad, + int num_sequences, + int w_dim, + size_t total_pad, + int context_length, + int context_start, + int begin_pad) { + CHECK_NOTNULL(out_grad); + CHECK_NOTNULL(sequence); + CHECK_NOTNULL(w_grad); + + int threads_x = 32; + int threads_y = 32; + int blocks_x = total_pad * ((w_dim + threads_x - 1) / threads_x); + dim3 threads(threads_x, threads_y); + dim3 grid(blocks_x, 1); + + KeContextProjectionBackwardWeight<32, 32> + <<< grid, threads, 0, STREAM_DEFAULT >>> + (out_grad, sequence, w_grad, num_sequences, w_dim, + context_length, context_start, begin_pad); + CHECK_SYNC("hl_context_projection_backward_weight failed"); +} + +template <> +void ContextProjectionBackwardWeight(Tensor& out_grad, + Tensor& w_grad, + const Tensor& sequence, + size_t context_length, + int context_start, + size_t total_pad, + size_t begin_pad) { + CHECK(w_grad.getData() && out_grad.getData()); + CHECK_EQ(out_grad.dims_.size(), 2); + CHECK_EQ(w_grad.dims_.size(), 2); + CHECK_EQ(sequence.dims_.size(), 1); + CHECK_EQ(out_grad.dims_[1], w_grad.dims_[1] * context_length); + + hl_context_projection_backward_weight(out_grad.getData(), + reinterpret_cast(sequence.getData()), + w_grad.getData(), + sequence.dims_[0] - 1, + w_grad.dims_[1], + total_pad, + context_length, + context_start, + begin_pad); +} + } // namespace paddle diff --git a/paddle/function/context_projection_op_test.cpp b/paddle/function/context_projection_op_test.cpp index 98784471ae..997bcb1bd2 100644 --- a/paddle/function/context_projection_op_test.cpp +++ b/paddle/function/context_projection_op_test.cpp @@ -77,7 +77,100 @@ void testMatrixProjectionForward(int context_start, autotest::TensorCheckEqual(cpu_out, gpu_out); } -TEST(ContextProjectionForward, projection) { +void testMatrixProjectionBackward(int context_start, + int context_length, + bool is_padding, + size_t batch_size, + size_t input_dim) { + size_t pad = std::max(0, -context_start) + + std::max(0, (int)(context_start + context_length - 1)); + if (pad == 0) is_padding = false; + + std::shared_ptr cpu_func( + FunctionBase::funcRegistrar_.createByType( + "ContextProjectionBackward-CPU")); + FuncConfig cpu_config; + cpu_config.set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", std::max(0, -context_start)) + .set("is_padding", is_padding); + cpu_func->init(cpu_config); + + std::shared_ptr gpu_data_func( + FunctionBase::funcRegistrar_.createByType( + "ContextProjectionBackwardData-GPU")); + FuncConfig gpu_data_config; + gpu_data_config.set("context_length", context_length) + .set("context_start", context_start); + gpu_data_func->init(gpu_data_config); + + std::shared_ptr gpu_w_func( + FunctionBase::funcRegistrar_.createByType( + "ContextProjectionBackwardWeight-GPU")); + FuncConfig gpu_w_config; + gpu_w_config.set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", std::max(0, -context_start)) + .set("total_pad", pad); + gpu_w_func->init(gpu_w_config); + + CpuMatrix cpu_in_grad(batch_size, input_dim); + cpu_in_grad.randomizeUniform(); + GpuMatrix gpu_in_grad(batch_size, input_dim); + gpu_in_grad.copyFrom(cpu_in_grad); + + CpuMatrix cpu_out_grad(batch_size, input_dim * context_length); + cpu_out_grad.randomizeUniform(); + GpuMatrix gpu_out_grad(batch_size, input_dim * context_length); + gpu_out_grad.copyFrom(cpu_out_grad); + + IVectorPtr cpu_seq; + generateSequenceStartPositions(batch_size, cpu_seq); + IVectorPtr gpu_seq = IVector::create(cpu_seq->getSize(), true); + gpu_seq->copyFrom(*cpu_seq); + + auto cpu_w_grad = + is_padding ? std::make_shared(pad, input_dim) : nullptr; + auto gpu_w_grad = + is_padding ? std::make_shared(pad, input_dim) : nullptr; + if (is_padding) { + cpu_w_grad->randomizeUniform(); + gpu_w_grad->copyFrom(*cpu_w_grad); + } + + cpu_func->calc({Tensor(cpu_in_grad.getData(), Dims{batch_size, input_dim}), + Tensor(cpu_w_grad ? cpu_w_grad->getData() : nullptr, + Dims{pad, input_dim}), + Tensor(reinterpret_cast(cpu_seq->getData()), + Dims{cpu_seq->getSize()})}, + {Tensor(cpu_out_grad.getData(), + Dims{batch_size, input_dim * context_length})}, + {}); + + gpu_data_func->calc( + {Tensor(gpu_in_grad.getData(), Dims{batch_size, input_dim}), + Tensor(reinterpret_cast(gpu_seq->getData()), + Dims{gpu_seq->getSize()})}, + {Tensor(gpu_out_grad.getData(), + Dims{batch_size, input_dim * context_length})}, + {}); + + if (is_padding && gpu_w_grad) { + gpu_w_func->calc({Tensor(gpu_w_grad->getData(), Dims{pad, input_dim}), + Tensor(reinterpret_cast(gpu_seq->getData()), + Dims{gpu_seq->getSize()})}, + {Tensor(gpu_out_grad.getData(), + Dims{batch_size, input_dim * context_length})}, + {}); + } + + autotest::TensorCheckErr(cpu_in_grad, gpu_in_grad); + if (is_padding) { + autotest::TensorCheckErr(*cpu_w_grad, *gpu_w_grad); + } +} + +TEST(ContextProjection, projection) { for (auto context_start : {-5, -3, -1, 0, 3}) { for (auto context_length : {1, 2, 5, 7}) { for (auto trainable_padding : {false, true}) { @@ -93,6 +186,11 @@ TEST(ContextProjectionForward, projection) { trainable_padding, batch_size, input_dim); + testMatrixProjectionBackward(context_start, + context_length, + trainable_padding, + batch_size, + input_dim); } } } From f23a11702a7647ade469de674abe1227fe5000d7 Mon Sep 17 00:00:00 2001 From: xutianbing Date: Wed, 28 Dec 2016 20:55:00 -0800 Subject: [PATCH 078/119] add Context Projection functions to Layer's forward and backward, resolve merge conflicts --- ...jection_op.cpp => ContextProjectionOp.cpp} | 2 +- ..._projection_op.h => ContextProjectionOp.h} | 0 ...on_op_gpu.cu => ContextProjectionOpGpu.cu} | 4 +- ...p_test.cpp => ContextProjectionOpTest.cpp} | 0 paddle/function/Function.cpp | 4 +- paddle/gserver/layers/ContextProjection.cpp | 148 +++++++++++++----- paddle/gserver/layers/ContextProjection.h | 2 + paddle/gserver/layers/Projection.h | 24 +++ 8 files changed, 137 insertions(+), 47 deletions(-) rename paddle/function/{context_projection_op.cpp => ContextProjectionOp.cpp} (99%) rename paddle/function/{context_projection_op.h => ContextProjectionOp.h} (100%) rename paddle/function/{context_projection_op_gpu.cu => ContextProjectionOpGpu.cu} (99%) rename paddle/function/{context_projection_op_test.cpp => ContextProjectionOpTest.cpp} (100%) diff --git a/paddle/function/context_projection_op.cpp b/paddle/function/ContextProjectionOp.cpp similarity index 99% rename from paddle/function/context_projection_op.cpp rename to paddle/function/ContextProjectionOp.cpp index a6a85fb6a4..3ada0b727b 100644 --- a/paddle/function/context_projection_op.cpp +++ b/paddle/function/ContextProjectionOp.cpp @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -#include "context_projection_op.h" +#include "ContextProjectionOp.h" #include "paddle/math/Matrix.h" #include "paddle/math/Vector.h" diff --git a/paddle/function/context_projection_op.h b/paddle/function/ContextProjectionOp.h similarity index 100% rename from paddle/function/context_projection_op.h rename to paddle/function/ContextProjectionOp.h diff --git a/paddle/function/context_projection_op_gpu.cu b/paddle/function/ContextProjectionOpGpu.cu similarity index 99% rename from paddle/function/context_projection_op_gpu.cu rename to paddle/function/ContextProjectionOpGpu.cu index fdea433d07..4388262998 100644 --- a/paddle/function/context_projection_op_gpu.cu +++ b/paddle/function/ContextProjectionOpGpu.cu @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "hl_base.h" -#include "context_projection_op.h" +#include "ContextProjectionOp.h" namespace paddle { @@ -327,7 +327,7 @@ void ContextProjectionBackwardWeight(Tensor& out_grad, int context_start, size_t total_pad, size_t begin_pad) { - CHECK(w_grad.getData() && out_grad.getData()); + CHECK(w_grad.getData() && out_grad.getData() && sequence.getData()); CHECK_EQ(out_grad.dims_.size(), 2); CHECK_EQ(w_grad.dims_.size(), 2); CHECK_EQ(sequence.dims_.size(), 1); diff --git a/paddle/function/context_projection_op_test.cpp b/paddle/function/ContextProjectionOpTest.cpp similarity index 100% rename from paddle/function/context_projection_op_test.cpp rename to paddle/function/ContextProjectionOpTest.cpp diff --git a/paddle/function/Function.cpp b/paddle/function/Function.cpp index 215b3dbd83..6f82a8d053 100644 --- a/paddle/function/Function.cpp +++ b/paddle/function/Function.cpp @@ -60,14 +60,14 @@ FuncConfig& FuncConfig::set(const std::string& key, real v) { template <> FuncConfig& FuncConfig::set(const std::string& key, int v) { - CHECK(valueMap_.count(key) == 0) << "Duplicated value: " << key; + CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; valueMap_[key].i = v; return *this; } template <> FuncConfig& FuncConfig::set(const std::string& key, bool v) { - CHECK(valueMap_.count(key) == 0) << "Duplicated value: " << key; + CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; valueMap_[key].b = v; return *this; } diff --git a/paddle/gserver/layers/ContextProjection.cpp b/paddle/gserver/layers/ContextProjection.cpp index 51c0ae5cc9..d6af71824c 100644 --- a/paddle/gserver/layers/ContextProjection.cpp +++ b/paddle/gserver/layers/ContextProjection.cpp @@ -38,6 +38,53 @@ ContextProjection::ContextProjection(const ProjectionConfig& config, CHECK_EQ(inputDim * totalPad, parameter->getSize()); weight_.reset(new Weight(totalPad, inputDim, parameter)); } + // init forward_ and backward_ functions + init(); +} + +bool ContextProjection::init() { + size_t context_length = config_.context_length(); + int context_start = config_.context_start(); + bool is_padding = config_.trainable_padding(); + size_t total_pad = is_padding ? beginPad_ + endPad_ : 0; + if (!useGpu_) { // CPU functions + createFunction(forward_, + "ContextProjectionForward-CPU", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", beginPad_) + .set("is_padding", is_padding)); + createFunction(backward_, + "ContextProjectionBackward-CPU", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", beginPad_) + .set("is_padding", is_padding)); + } else { // GPU functions + createFunction(forward_, + "ContextProjectionForward-GPU", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", beginPad_) + .set("is_padding", is_padding)); + createFunction(backward_, + "ContextProjectionBackwardData-GPU", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start)); + + createFunction(backward_, + "ContextProjectionBackwardWeight-GPU", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", beginPad_) + .set("total_pad", total_pad)); + } + return true; } void ContextProjection::resetState() { @@ -78,25 +125,35 @@ LayerStatePtr ContextProjection::getState() { } void ContextProjection::forward() { - CHECK(in_->value); + CHECK(in_->value && out_->value); CHECK(in_->sequenceStartPositions); - auto startPositions = in_->sequenceStartPositions->getVector(useGpu_); - - int64_t inputDim = in_->value->getWidth(); - int64_t dim = out_->value->getWidth(); - CHECK_EQ(dim, inputDim * config_.context_length()); + size_t input_dim = in_->value->getWidth(); + size_t dim = out_->value->getWidth(); + CHECK_EQ(dim, input_dim * config_.context_length()); + size_t batch_size = in_->value->getHeight(); + CHECK_EQ(batch_size, out_->value->getHeight()); REGISTER_TIMER_INFO("ContextProjectionForward", getName().c_str()); - bool isPadding = config_.trainable_padding(); - out_->value->contextProjectionForward( - *(in_->value), - state_ ? state_.get() : isPadding ? weight_->getW().get() : nullptr, - *startPositions, - config_.context_length(), - config_.context_start(), - beginPad_, - state_ ? true : isPadding); + bool is_padding = config_.trainable_padding(); + /// first use state_, otherwise use weight_(padding false === w nullptr) + auto w_ptr = + state_ ? state_.get() : is_padding ? weight_->getW().get() : nullptr; + auto start_pos = in_->sequenceStartPositions; + /// if use state_ as weight_, w_ptr already has mem, so padding true + forward_[0]->init(FuncConfig() + .set("context_length", config_.context_length()) + .set("context_start", config_.context_start()) + .set("begin_pad", beginPad_) + .set("is_padding", state_ ? true : is_padding)); + forward_[0]->calc({Tensor(in_->value->getData(), Dims{batch_size, input_dim}), + Tensor(w_ptr ? w_ptr->getData() : nullptr, + Dims{w_ptr ? w_ptr->getHeight() : 0, input_dim}), + Tensor(reinterpret_cast( + const_cast(start_pos->getData(useGpu_))), + Dims{start_pos->getSize()})}, + {Tensor(out_->value->getData(), Dims{batch_size, dim})}, + {}); if (state_ && config_.context_start() < 0) { CHECK_EQ(1, in_->getNumSequences()); @@ -118,39 +175,46 @@ void ContextProjection::forward() { } void ContextProjection::backward(const UpdateCallback& callback) { - CHECK(in_->value); - int64_t inputDim = in_->value->getWidth(); - int64_t dim = out_->value->getWidth(); - CHECK_EQ(dim, inputDim * config_.context_length()); - auto startPositions = in_->sequenceStartPositions->getVector(useGpu_); + CHECK(in_->value && out_->value && out_->grad); + size_t input_dim = in_->value->getWidth(); + size_t dim = out_->value->getWidth(); + CHECK_EQ(dim, input_dim * config_.context_length()); + size_t batch_size = in_->value->getHeight(); + CHECK_EQ(batch_size, out_->value->getHeight()); REGISTER_TIMER_INFO("ContextProjectionBackward", getName().c_str()); - bool isPadding = config_.trainable_padding(); + bool is_padding = config_.trainable_padding(); + auto start_pos = in_->sequenceStartPositions; if (!out_->grad->useGpu()) { - out_->grad->contextProjectionBackward( - in_->grad.get(), - isPadding ? weight_->getWGrad().get() : nullptr, - *startPositions, - config_.context_length(), - config_.context_start(), - beginPad_, - isPadding); + auto w_ptr = is_padding ? weight_->getWGrad() : nullptr; + backward_[0]->calc({Tensor(in_->grad ? in_->grad->getData() : nullptr, + Dims{batch_size, input_dim}), + Tensor(w_ptr ? w_ptr->getData() : nullptr, + Dims{w_ptr ? w_ptr->getHeight() : 0, input_dim}), + Tensor(reinterpret_cast(const_cast( + start_pos->getData(useGpu_))), + Dims{start_pos->getSize()})}, + {Tensor(out_->grad->getData(), Dims{batch_size, dim})}, + {}); } else { if (in_->grad) { - out_->grad->contextProjectionBackwardData(*(in_->grad), - *startPositions, - config_.context_length(), - config_.context_start()); + backward_[0]->calc( + {Tensor(in_->grad->getData(), Dims{batch_size, input_dim}), + Tensor(reinterpret_cast( + const_cast(start_pos->getData(useGpu_))), + Dims{start_pos->getSize()})}, + {Tensor(out_->grad->getData(), Dims{batch_size, dim})}, + {}); } - - if (isPadding && weight_->getWGrad()) { - out_->grad->contextProjectionBackwardWeight( - *(weight_->getWGrad()), - *startPositions, - config_.context_length(), - config_.context_start(), - weight_->getWGrad()->getHeight(), - beginPad_); + if (is_padding && weight_->getWGrad()) { + backward_[1]->calc( + {Tensor(weight_->getWGrad()->getData(), + Dims{weight_->getWGrad()->getHeight(), input_dim}), + Tensor(reinterpret_cast( + const_cast(start_pos->getData(useGpu_))), + Dims{start_pos->getSize()})}, + {Tensor(out_->grad->getData(), Dims{batch_size, dim})}, + {}); } } diff --git a/paddle/gserver/layers/ContextProjection.h b/paddle/gserver/layers/ContextProjection.h index 2df43bd04f..c87d6ed1d6 100644 --- a/paddle/gserver/layers/ContextProjection.h +++ b/paddle/gserver/layers/ContextProjection.h @@ -61,6 +61,8 @@ public: virtual LayerStatePtr getState(); + virtual bool init(); + protected: std::unique_ptr weight_; /// number of extra timesteps added at the beginning diff --git a/paddle/gserver/layers/Projection.h b/paddle/gserver/layers/Projection.h index 8cd8042479..1e9f60706f 100644 --- a/paddle/gserver/layers/Projection.h +++ b/paddle/gserver/layers/Projection.h @@ -88,11 +88,31 @@ public: */ virtual LayerStatePtr getState() { return nullptr; } + /** + * init forward_ and backward_ functions + */ + virtual bool init() { return true; } + /** * Get output size of projection. */ size_t getOutputSize() const { return config_.output_size(); } +protected: + /** + * Create layer function. Function is called in forward or backward. + * \param function, Layer::forward_ or Layer::backward_ + * \param name, function name, include -GPU or -CPU + * \param config, initialization configuration for the function + */ + void createFunction(std::vector>& function, + const std::string& name, + const FuncConfig& config) { + function.emplace_back(FunctionBase::funcRegistrar_.createByType(name)); + auto& func = function.back(); + func->init(config); + } + protected: /// Config of projection ProjectionConfig config_; @@ -106,5 +126,9 @@ protected: const Argument* out_; /// Store `passType` passed to forward() PassType passType_; + /// Layer forward function + std::vector> forward_; + /// Layer backward function + std::vector> backward_; }; } // namespace paddle From 2c37ad7edc2eaf11c112ff65f939d6b789065756 Mon Sep 17 00:00:00 2001 From: xutianbing Date: Wed, 28 Dec 2016 22:46:45 -0800 Subject: [PATCH 079/119] combine data/weight to ContextProjectionBackward for clean code --- paddle/function/ContextProjectionOp.cpp | 23 +++-- paddle/function/ContextProjectionOp.h | 7 +- paddle/function/ContextProjectionOpGpu.cu | 28 ++++++ paddle/function/ContextProjectionOpTest.cpp | 64 ++++---------- paddle/gserver/layers/ContextProjection.cpp | 96 ++++++--------------- paddle/gserver/layers/Projection.h | 10 ++- 6 files changed, 101 insertions(+), 127 deletions(-) diff --git a/paddle/function/ContextProjectionOp.cpp b/paddle/function/ContextProjectionOp.cpp index 3ada0b727b..40852e1ab4 100644 --- a/paddle/function/ContextProjectionOp.cpp +++ b/paddle/function/ContextProjectionOp.cpp @@ -127,13 +127,14 @@ private: template <> void ContextProjectionBackward(Tensor& out_grad, - const Tensor& in_grad, - const Tensor& w_grad, + Tensor& in_grad, + Tensor& w_grad, const Tensor& sequence, size_t context_length, int context_start, size_t begin_pad, - bool is_padding) { + bool is_padding, + size_t total_pad) { CHECK(out_grad.getData() && sequence.getData()); CHECK_EQ(out_grad.dims_.size(), 2); CHECK_EQ(in_grad.dims_.size(), 2); @@ -202,8 +203,8 @@ void ContextProjectionBackward(Tensor& out_grad, } /** - * \param inputs[0] input value. - * \param inputs[1] input weight. + * \param inputs[0] input grad. + * \param inputs[1] weight grad. * \param inputs[2] input sequence. * \param outputs[0] output value. */ @@ -215,6 +216,7 @@ public: context_start_ = config.get("context_start"); begin_pad_ = config.get("begin_pad"); is_padding_ = config.get("is_padding"); + total_pad_ = config.get("total_pad"); } void calc(const Arguments& inputs, @@ -225,13 +227,14 @@ public: CHECK_EQ(0, inouts.size()); ContextProjectionBackward((Tensor&)outputs[0], - inputs[0], - inputs[1], + (Tensor&)inputs[0], + (Tensor&)inputs[1], inputs[2], context_length_, context_start_, begin_pad_, - is_padding_); + is_padding_, + total_pad_); } private: @@ -239,6 +242,7 @@ private: int context_start_; size_t begin_pad_; bool is_padding_; + size_t total_pad_; }; /** @@ -321,6 +325,9 @@ REGISTER_TYPED_FUNC(ContextProjectionBackward, REGISTER_TYPED_FUNC(ContextProjectionForward, GPU, ContextProjectionForwardFunc); +REGISTER_TYPED_FUNC(ContextProjectionBackward, + GPU, + ContextProjectionBackwardFunc); REGISTER_TYPED_FUNC(ContextProjectionBackwardData, GPU, ContextProjectionBackwardDataFunc); diff --git a/paddle/function/ContextProjectionOp.h b/paddle/function/ContextProjectionOp.h index 5f4e0761db..e0f1beb496 100644 --- a/paddle/function/ContextProjectionOp.h +++ b/paddle/function/ContextProjectionOp.h @@ -56,13 +56,14 @@ void ContextProjectionForward(Tensor& output, */ template void ContextProjectionBackward(Tensor& out_grad, - const Tensor& in_grad, - const Tensor& w_grad, + Tensor& in_grad, + Tensor& w_grad, const Tensor& sequence, size_t context_length, int context_start, size_t begin_pad, - bool is_padding); + bool is_padding, + size_t total_pad); template void ContextProjectionBackwardData(Tensor& out_grad, diff --git a/paddle/function/ContextProjectionOpGpu.cu b/paddle/function/ContextProjectionOpGpu.cu index 4388262998..1e5916002c 100644 --- a/paddle/function/ContextProjectionOpGpu.cu +++ b/paddle/function/ContextProjectionOpGpu.cu @@ -344,4 +344,32 @@ void ContextProjectionBackwardWeight(Tensor& out_grad, begin_pad); } +template <> +void ContextProjectionBackward(Tensor& out_grad, + Tensor& in_grad, + Tensor& w_grad, + const Tensor& sequence, + size_t context_length, + int context_start, + size_t begin_pad, + bool is_padding, + size_t total_pad) { + if (in_grad.getData()) { + ContextProjectionBackwardData(out_grad, + in_grad, + sequence, + context_length, + context_start); + } + if (is_padding && w_grad.getData()) { + ContextProjectionBackwardWeight(out_grad, + w_grad, + sequence, + context_length, + context_start, + total_pad, + begin_pad); + } +} + } // namespace paddle diff --git a/paddle/function/ContextProjectionOpTest.cpp b/paddle/function/ContextProjectionOpTest.cpp index 997bcb1bd2..372fc21cf1 100644 --- a/paddle/function/ContextProjectionOpTest.cpp +++ b/paddle/function/ContextProjectionOpTest.cpp @@ -86,33 +86,13 @@ void testMatrixProjectionBackward(int context_start, std::max(0, (int)(context_start + context_length - 1)); if (pad == 0) is_padding = false; - std::shared_ptr cpu_func( - FunctionBase::funcRegistrar_.createByType( - "ContextProjectionBackward-CPU")); - FuncConfig cpu_config; - cpu_config.set("context_length", context_length) - .set("context_start", context_start) - .set("begin_pad", std::max(0, -context_start)) - .set("is_padding", is_padding); - cpu_func->init(cpu_config); - - std::shared_ptr gpu_data_func( - FunctionBase::funcRegistrar_.createByType( - "ContextProjectionBackwardData-GPU")); - FuncConfig gpu_data_config; - gpu_data_config.set("context_length", context_length) - .set("context_start", context_start); - gpu_data_func->init(gpu_data_config); - - std::shared_ptr gpu_w_func( - FunctionBase::funcRegistrar_.createByType( - "ContextProjectionBackwardWeight-GPU")); - FuncConfig gpu_w_config; - gpu_w_config.set("context_length", context_length) - .set("context_start", context_start) - .set("begin_pad", std::max(0, -context_start)) - .set("total_pad", pad); - gpu_w_func->init(gpu_w_config); + FunctionCompare compare("ContextProjectionBackward", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", std::max(0, -context_start)) + .set("is_padding", is_padding) + .set("total_pad", pad)); CpuMatrix cpu_in_grad(batch_size, input_dim); cpu_in_grad.randomizeUniform(); @@ -138,32 +118,26 @@ void testMatrixProjectionBackward(int context_start, gpu_w_grad->copyFrom(*cpu_w_grad); } - cpu_func->calc({Tensor(cpu_in_grad.getData(), Dims{batch_size, input_dim}), - Tensor(cpu_w_grad ? cpu_w_grad->getData() : nullptr, - Dims{pad, input_dim}), - Tensor(reinterpret_cast(cpu_seq->getData()), - Dims{cpu_seq->getSize()})}, - {Tensor(cpu_out_grad.getData(), - Dims{batch_size, input_dim * context_length})}, - {}); + compare.getCpuFunction()->calc( + {Tensor(cpu_in_grad.getData(), Dims{batch_size, input_dim}), + Tensor(cpu_w_grad ? cpu_w_grad->getData() : nullptr, + Dims{pad, input_dim}), + Tensor(reinterpret_cast(cpu_seq->getData()), + Dims{cpu_seq->getSize()})}, + {Tensor(cpu_out_grad.getData(), + Dims{batch_size, input_dim * context_length})}, + {}); - gpu_data_func->calc( + compare.getGpuFunction()->calc( {Tensor(gpu_in_grad.getData(), Dims{batch_size, input_dim}), + Tensor(gpu_w_grad ? gpu_w_grad->getData() : nullptr, + Dims{pad, input_dim}), Tensor(reinterpret_cast(gpu_seq->getData()), Dims{gpu_seq->getSize()})}, {Tensor(gpu_out_grad.getData(), Dims{batch_size, input_dim * context_length})}, {}); - if (is_padding && gpu_w_grad) { - gpu_w_func->calc({Tensor(gpu_w_grad->getData(), Dims{pad, input_dim}), - Tensor(reinterpret_cast(gpu_seq->getData()), - Dims{gpu_seq->getSize()})}, - {Tensor(gpu_out_grad.getData(), - Dims{batch_size, input_dim * context_length})}, - {}); - } - autotest::TensorCheckErr(cpu_in_grad, gpu_in_grad); if (is_padding) { autotest::TensorCheckErr(*cpu_w_grad, *gpu_w_grad); diff --git a/paddle/gserver/layers/ContextProjection.cpp b/paddle/gserver/layers/ContextProjection.cpp index d6af71824c..37e951a1e3 100644 --- a/paddle/gserver/layers/ContextProjection.cpp +++ b/paddle/gserver/layers/ContextProjection.cpp @@ -47,43 +47,23 @@ bool ContextProjection::init() { int context_start = config_.context_start(); bool is_padding = config_.trainable_padding(); size_t total_pad = is_padding ? beginPad_ + endPad_ : 0; - if (!useGpu_) { // CPU functions - createFunction(forward_, - "ContextProjectionForward-CPU", - FuncConfig() - .set("context_length", context_length) - .set("context_start", context_start) - .set("begin_pad", beginPad_) - .set("is_padding", is_padding)); - createFunction(backward_, - "ContextProjectionBackward-CPU", - FuncConfig() - .set("context_length", context_length) - .set("context_start", context_start) - .set("begin_pad", beginPad_) - .set("is_padding", is_padding)); - } else { // GPU functions - createFunction(forward_, - "ContextProjectionForward-GPU", - FuncConfig() - .set("context_length", context_length) - .set("context_start", context_start) - .set("begin_pad", beginPad_) - .set("is_padding", is_padding)); - createFunction(backward_, - "ContextProjectionBackwardData-GPU", - FuncConfig() - .set("context_length", context_length) - .set("context_start", context_start)); - - createFunction(backward_, - "ContextProjectionBackwardWeight-GPU", - FuncConfig() - .set("context_length", context_length) - .set("context_start", context_start) - .set("begin_pad", beginPad_) - .set("total_pad", total_pad)); - } + + createFunction(forward_, + "ContextProjectionForward", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", beginPad_) + .set("is_padding", is_padding)); + createFunction(backward_, + "ContextProjectionBackward", + FuncConfig() + .set("context_length", context_length) + .set("context_start", context_start) + .set("begin_pad", beginPad_) + .set("is_padding", is_padding) + .set("total_pad", total_pad)); + return true; } @@ -185,38 +165,16 @@ void ContextProjection::backward(const UpdateCallback& callback) { REGISTER_TIMER_INFO("ContextProjectionBackward", getName().c_str()); bool is_padding = config_.trainable_padding(); auto start_pos = in_->sequenceStartPositions; - if (!out_->grad->useGpu()) { - auto w_ptr = is_padding ? weight_->getWGrad() : nullptr; - backward_[0]->calc({Tensor(in_->grad ? in_->grad->getData() : nullptr, - Dims{batch_size, input_dim}), - Tensor(w_ptr ? w_ptr->getData() : nullptr, - Dims{w_ptr ? w_ptr->getHeight() : 0, input_dim}), - Tensor(reinterpret_cast(const_cast( - start_pos->getData(useGpu_))), - Dims{start_pos->getSize()})}, - {Tensor(out_->grad->getData(), Dims{batch_size, dim})}, - {}); - } else { - if (in_->grad) { - backward_[0]->calc( - {Tensor(in_->grad->getData(), Dims{batch_size, input_dim}), - Tensor(reinterpret_cast( - const_cast(start_pos->getData(useGpu_))), - Dims{start_pos->getSize()})}, - {Tensor(out_->grad->getData(), Dims{batch_size, dim})}, - {}); - } - if (is_padding && weight_->getWGrad()) { - backward_[1]->calc( - {Tensor(weight_->getWGrad()->getData(), - Dims{weight_->getWGrad()->getHeight(), input_dim}), - Tensor(reinterpret_cast( - const_cast(start_pos->getData(useGpu_))), - Dims{start_pos->getSize()})}, - {Tensor(out_->grad->getData(), Dims{batch_size, dim})}, - {}); - } - } + auto w_ptr = is_padding ? weight_->getWGrad() : nullptr; + backward_[0]->calc({Tensor(in_->grad ? in_->grad->getData() : nullptr, + Dims{batch_size, input_dim}), + Tensor(w_ptr ? w_ptr->getData() : nullptr, + Dims{w_ptr ? w_ptr->getHeight() : 0, input_dim}), + Tensor(reinterpret_cast( + const_cast(start_pos->getData(useGpu_))), + Dims{start_pos->getSize()})}, + {Tensor(out_->grad->getData(), Dims{batch_size, dim})}, + {}); if (config_.trainable_padding()) { weight_->getParameterPtr()->incUpdate(callback); diff --git a/paddle/gserver/layers/Projection.h b/paddle/gserver/layers/Projection.h index 1e9f60706f..778a7fe13d 100644 --- a/paddle/gserver/layers/Projection.h +++ b/paddle/gserver/layers/Projection.h @@ -102,13 +102,19 @@ protected: /** * Create layer function. Function is called in forward or backward. * \param function, Layer::forward_ or Layer::backward_ - * \param name, function name, include -GPU or -CPU + * \param name, function name * \param config, initialization configuration for the function */ void createFunction(std::vector>& function, const std::string& name, const FuncConfig& config) { - function.emplace_back(FunctionBase::funcRegistrar_.createByType(name)); + if (useGpu_) { + function.emplace_back( + FunctionBase::funcRegistrar_.createByType(name + "-GPU")); + } else { + function.emplace_back( + FunctionBase::funcRegistrar_.createByType(name + "-CPU")); + } auto& func = function.back(); func->init(config); } From ea4d08dab6b76a685e9277f28daf6b594912b97f Mon Sep 17 00:00:00 2001 From: xutianbing Date: Thu, 29 Dec 2016 15:37:00 -0800 Subject: [PATCH 080/119] update interface of context projection functions, Tensor -> Matrix/Vector --- paddle/function/ContextProjectionOp.cpp | 185 ++++++++++++-------- paddle/function/ContextProjectionOp.h | 45 ++--- paddle/function/ContextProjectionOpGpu.cu | 130 ++++++-------- paddle/function/ContextProjectionOpTest.cpp | 3 +- paddle/function/Function.h | 13 ++ paddle/gserver/layers/ContextProjection.cpp | 12 +- 6 files changed, 207 insertions(+), 181 deletions(-) diff --git a/paddle/function/ContextProjectionOp.cpp b/paddle/function/ContextProjectionOp.cpp index 40852e1ab4..bd367a859e 100644 --- a/paddle/function/ContextProjectionOp.cpp +++ b/paddle/function/ContextProjectionOp.cpp @@ -19,35 +19,17 @@ limitations under the License. */ namespace paddle { template <> -void ContextProjectionForward(Tensor& output, - const Tensor& input, - const Tensor& weight, - const Tensor& sequence, +void ContextProjectionForward(CpuMatrix* out_mat, + const CpuMatrix* input_mat, + const CpuMatrix* weight_mat, + const CpuIVector& seq_vec, size_t context_length, int context_start, - size_t begin_pad, - bool is_padding) { - CHECK(output.getData() && input.getData() && sequence.getData()); - CHECK_EQ(output.dims_.size(), 2); - CHECK_EQ(input.dims_.size(), 2); - CHECK_EQ(weight.dims_.size(), 2); - CHECK_EQ(sequence.dims_.size(), 1); - - auto out_mat = std::make_shared( - output.getData(), output.dims_[0], output.dims_[1]); - const auto in_mat = std::make_shared( - input.getData(), input.dims_[0], input.dims_[1]); - const auto weight_mat = - !weight.getData() - ? nullptr - : std::make_shared( - weight.getData(), weight.dims_[0], weight.dims_[1]); - CpuIVector seq_vec(sequence.dims_[0], - reinterpret_cast(sequence.getData())); - CHECK_EQ(out_mat->getWidth(), in_mat->getWidth() * context_length); - + size_t begin_pad) { const int* starts = seq_vec.getData(); const size_t num_sequences = seq_vec.getSize() - 1; + auto w_mat = const_cast(weight_mat); + auto in_mat = const_cast(input_mat); for (size_t i = 0; i < num_sequences; ++i) { for (size_t j = 0; j < context_length; ++j) { int begin = starts[i] + context_start + j; @@ -58,8 +40,8 @@ void ContextProjectionForward(Tensor& output, int64_t pad_size = std::min(starts[i] - begin, starts[i + 1] - starts[i]); MatrixPtr mat = out_mat->subMatrix(starts[i], pad_size); - if (is_padding && weight_mat) { - MatrixPtr sub = weight_mat->subMatrix(j, pad_size); + if (w_mat) { + MatrixPtr sub = w_mat->subMatrix(j, pad_size); mat->addAtOffset(*sub, j * in_mat->getWidth()); } dst_begin = starts[i] + pad_size; @@ -69,8 +51,8 @@ void ContextProjectionForward(Tensor& output, int64_t pad_size = std::min(end - starts[i + 1], starts[i + 1] - starts[i]); MatrixPtr mat = out_mat->subMatrix(starts[i + 1] - pad_size, pad_size); - if (is_padding && weight_mat) { - MatrixPtr sub = weight_mat->subMatrix( + if (w_mat) { + MatrixPtr sub = w_mat->subMatrix( begin_pad + context_start + j - pad_size, pad_size); mat->addAtOffset(*sub, j * in_mat->getWidth()); } @@ -98,7 +80,6 @@ public: context_length_ = config.get("context_length"); context_start_ = config.get("context_start"); begin_pad_ = config.get("begin_pad"); - is_padding_ = config.get("is_padding"); } void calc(const Arguments& inputs, @@ -108,59 +89,58 @@ public: CHECK_EQ(1, outputs.size()); CHECK_EQ(0, inouts.size()); - ContextProjectionForward((Tensor&)outputs[0], - inputs[0], - inputs[1], - inputs[2], + CHECK(outputs[0].getData() && inputs[0].getData() && inputs[2].getData()); + CHECK_EQ(outputs[0].dims_.size(), 2); + CHECK_EQ(inputs[0].dims_.size(), 2); + CHECK_EQ(inputs[1].dims_.size(), 2); + CHECK_EQ(inputs[2].dims_.size(), 1); + /// dim of output = dim of input * context_length + CHECK_EQ(outputs[0].dims_[1], inputs[0].dims_[1] * context_length_); + /// dim of input == dim of weight + CHECK_EQ(inputs[0].dims_[1], inputs[1].dims_[1]); + /// input and output has the same batch_size + CHECK_EQ(inputs[0].dims_[0], outputs[0].dims_[0]); + + auto out_mat = std::make_shared::type>( + outputs[0].getData(), outputs[0].dims_[0], outputs[0].dims_[1]); + const auto in_mat = std::make_shared::type>( + inputs[0].getData(), inputs[0].dims_[0], inputs[0].dims_[1]); + const auto w_mat = + !inputs[1].getData() + ? nullptr + : std::make_shared::type>( + inputs[1].getData(), inputs[1].dims_[0], inputs[1].dims_[1]); + typename SequenceT::type seq_vec( + inputs[2].dims_[0], reinterpret_cast(inputs[2].getData())); + + ContextProjectionForward(out_mat.get(), + in_mat.get(), + w_mat.get(), + seq_vec, context_length_, context_start_, - begin_pad_, - is_padding_); + begin_pad_); } private: size_t context_length_; int context_start_; size_t begin_pad_; - bool is_padding_; }; template <> -void ContextProjectionBackward(Tensor& out_grad, - Tensor& in_grad, - Tensor& w_grad, - const Tensor& sequence, +void ContextProjectionBackward(CpuMatrix* out_grad_mat, + CpuMatrix* in_grad_mat, + CpuMatrix* w_grad_mat, + const CpuIVector& seq_vec, size_t context_length, int context_start, size_t begin_pad, bool is_padding, size_t total_pad) { - CHECK(out_grad.getData() && sequence.getData()); - CHECK_EQ(out_grad.dims_.size(), 2); - CHECK_EQ(in_grad.dims_.size(), 2); - CHECK_EQ(w_grad.dims_.size(), 2); - CHECK_EQ(sequence.dims_.size(), 1); - - auto out_grad_mat = std::make_shared( - out_grad.getData(), out_grad.dims_[0], out_grad.dims_[1]); - const auto in_grad_mat = - !in_grad.getData() - ? nullptr - : std::make_shared( - in_grad.getData(), in_grad.dims_[0], in_grad.dims_[1]); - const auto w_grad_mat = - !w_grad.getData() - ? nullptr - : std::make_shared( - w_grad.getData(), w_grad.dims_[0], w_grad.dims_[1]); - CpuIVector seq_vec(sequence.dims_[0], - reinterpret_cast(sequence.getData())); - CHECK_EQ(out_grad_mat->getWidth(), in_grad_mat->getWidth() * context_length); - + CHECK(out_grad_mat); size_t input_dim = in_grad_mat ? in_grad_mat->getWidth() : w_grad_mat ? w_grad_mat->getWidth() : 0; - CHECK_EQ(out_grad_mat->getWidth(), input_dim * context_length); - const int* starts = seq_vec.getData(); size_t num_sequences = seq_vec.getSize() - 1; for (size_t i = 0; i < num_sequences; ++i) { @@ -226,10 +206,38 @@ public: CHECK_EQ(1, outputs.size()); CHECK_EQ(0, inouts.size()); - ContextProjectionBackward((Tensor&)outputs[0], - (Tensor&)inputs[0], - (Tensor&)inputs[1], - inputs[2], + CHECK(outputs[0].getData() && inputs[2].getData()); + CHECK_EQ(outputs[0].dims_.size(), 2); + CHECK_EQ(inputs[0].dims_.size(), 2); + CHECK_EQ(inputs[1].dims_.size(), 2); + CHECK_EQ(inputs[2].dims_.size(), 1); + + /// dim of input == dim of weight + CHECK_EQ(inputs[0].dims_[1], inputs[1].dims_[1]); + /// input and output has the same batch_size + CHECK_EQ(inputs[0].dims_[0], outputs[0].dims_[0]); + /// dim of output = dim of input * context_length + CHECK_EQ(outputs[0].dims_[1], inputs[0].dims_[1] * context_length_); + + auto out_grad_mat = std::make_shared::type>( + outputs[0].getData(), outputs[0].dims_[0], outputs[0].dims_[1]); + auto in_grad_mat = + !inputs[0].getData() + ? nullptr + : std::make_shared::type>( + inputs[0].getData(), inputs[0].dims_[0], inputs[0].dims_[1]); + auto w_grad_mat = + !inputs[1].getData() + ? nullptr + : std::make_shared::type>( + inputs[1].getData(), inputs[1].dims_[0], inputs[1].dims_[1]); + typename SequenceT::type seq_vec( + inputs[2].dims_[0], reinterpret_cast(inputs[2].getData())); + + ContextProjectionBackward(out_grad_mat.get(), + in_grad_mat ? in_grad_mat.get() : nullptr, + w_grad_mat ? w_grad_mat.get() : nullptr, + seq_vec, context_length_, context_start_, begin_pad_, @@ -264,10 +272,24 @@ public: CHECK_EQ(2, inputs.size()); CHECK_EQ(1, outputs.size()); CHECK_EQ(0, inouts.size()); + CHECK(inputs[0].getData() && outputs[0].getData() && inputs[1].getData()); + CHECK_EQ(outputs[0].dims_.size(), 2); + CHECK_EQ(inputs[0].dims_.size(), 2); + CHECK_EQ(inputs[1].dims_.size(), 1); + CHECK_EQ(outputs[0].dims_[1], inputs[0].dims_[1] * context_length_); + /// input and output has the same batch_size + CHECK_EQ(inputs[0].dims_[0], outputs[0].dims_[0]); - ContextProjectionBackwardData((Tensor&)outputs[0], - (Tensor&)inputs[0], - inputs[1], + auto out_grad_mat = std::make_shared::type>( + outputs[0].getData(), outputs[0].dims_[0], outputs[0].dims_[1]); + const auto in_grad_mat = std::make_shared::type>( + inputs[0].getData(), inputs[0].dims_[0], inputs[0].dims_[1]); + typename SequenceT::type seq_vec( + inputs[1].dims_[0], reinterpret_cast(inputs[1].getData())); + + ContextProjectionBackwardData(out_grad_mat.get(), + in_grad_mat.get(), + seq_vec, context_length_, context_start_); } @@ -299,9 +321,22 @@ public: CHECK_EQ(1, outputs.size()); CHECK_EQ(0, inouts.size()); - ContextProjectionBackwardWeight((Tensor&)outputs[0], - (Tensor&)inputs[0], - inputs[1], + CHECK(inputs[0].getData() && outputs[0].getData() && inputs[1].getData()); + CHECK_EQ(outputs[0].dims_.size(), 2); + CHECK_EQ(inputs[0].dims_.size(), 2); + CHECK_EQ(inputs[1].dims_.size(), 1); + CHECK_EQ(outputs[0].dims_[1], inputs[0].dims_[1] * context_length_); + + auto out_grad_mat = std::make_shared::type>( + outputs[0].getData(), outputs[0].dims_[0], outputs[0].dims_[1]); + auto w_grad_mat = std::make_shared::type>( + inputs[0].getData(), inputs[0].dims_[0], inputs[0].dims_[1]); + typename SequenceT::type seq_vec( + inputs[1].dims_[0], reinterpret_cast(inputs[1].getData())); + + ContextProjectionBackwardWeight(out_grad_mat.get(), + w_grad_mat.get(), + seq_vec, context_length_, context_start_, total_pad_, diff --git a/paddle/function/ContextProjectionOp.h b/paddle/function/ContextProjectionOp.h index e0f1beb496..93eb050fde 100644 --- a/paddle/function/ContextProjectionOp.h +++ b/paddle/function/ContextProjectionOp.h @@ -32,14 +32,13 @@ namespace paddle { * */ template -void ContextProjectionForward(Tensor& output, - const Tensor& input, - const Tensor& weight, - const Tensor& sequence, +void ContextProjectionForward(typename MatrixT::type* output, + const typename MatrixT::type* input, + const typename MatrixT::type* weight, + const typename SequenceT::type& sequence, size_t context_length, int context_start, - size_t begin_pad, - bool is_padding); + size_t begin_pad); /** * \brief Context Projection Backward. @@ -55,10 +54,10 @@ void ContextProjectionForward(Tensor& output, * */ template -void ContextProjectionBackward(Tensor& out_grad, - Tensor& in_grad, - Tensor& w_grad, - const Tensor& sequence, +void ContextProjectionBackward(typename MatrixT::type* out_grad, + typename MatrixT::type* in_grad, + typename MatrixT::type* w_grad, + const typename SequenceT::type& seq_vec, size_t context_length, int context_start, size_t begin_pad, @@ -66,19 +65,21 @@ void ContextProjectionBackward(Tensor& out_grad, size_t total_pad); template -void ContextProjectionBackwardData(Tensor& out_grad, - Tensor& in_grad, - const Tensor& sequence, - size_t context_length, - int context_start); +void ContextProjectionBackwardData( + typename MatrixT::type* out_grad, + typename MatrixT::type* in_grad, + const typename SequenceT::type& sequence, + size_t context_length, + int context_start); template -void ContextProjectionBackwardWeight(Tensor& out_grad, - Tensor& w_grad, - const Tensor& sequence, - size_t context_length, - int context_start, - size_t total_pad, - size_t begin_pad); +void ContextProjectionBackwardWeight( + typename MatrixT::type* out_grad, + typename MatrixT::type* w_grad, + const typename SequenceT::type& seq_vec, + size_t context_length, + int context_start, + size_t total_pad, + size_t begin_pad); } // namespace paddle diff --git a/paddle/function/ContextProjectionOpGpu.cu b/paddle/function/ContextProjectionOpGpu.cu index 1e5916002c..7c4ebacdbf 100644 --- a/paddle/function/ContextProjectionOpGpu.cu +++ b/paddle/function/ContextProjectionOpGpu.cu @@ -75,18 +75,16 @@ __global__ void KeContextProjectionForward(const real* input, void hl_context_projection_forward(const real* input, const int* sequence, - real* weight, + const real* weight, real* output, int num_sequences, int input_dim, int context_length, int context_start, - int begin_pad, - bool is_padding) { + int begin_pad) { CHECK_NOTNULL(input); CHECK_NOTNULL(sequence); CHECK_NOTNULL(output); - CHECK(!is_padding || weight); int block_size = 128; int blocks_x = num_sequences; @@ -94,7 +92,7 @@ void hl_context_projection_forward(const real* input, dim3 threads(block_size, 1); dim3 grid(blocks_x, blocks_y); - if (is_padding) { + if (weight) { KeContextProjectionForward<<< grid, threads, 0, STREAM_DEFAULT >>> (input, sequence, weight, output, input_dim, context_length, context_start, begin_pad); @@ -107,31 +105,23 @@ void hl_context_projection_forward(const real* input, } template <> -void ContextProjectionForward(Tensor& output, - const Tensor& input, - const Tensor& weight, - const Tensor& sequence, +void ContextProjectionForward(GpuMatrix* output, + const GpuMatrix* input, + const GpuMatrix* weight, + const GpuIVector& sequence, size_t context_length, int context_start, - size_t begin_pad, - bool is_padding) { - CHECK(output.getData() && input.getData() && sequence.getData()); - CHECK_EQ(output.dims_.size(), 2); - CHECK_EQ(input.dims_.size(), 2); - CHECK_EQ(weight.dims_.size(), 2); - CHECK_EQ(sequence.dims_.size(), 1); - CHECK_EQ(output.dims_[1], input.dims_[1] * context_length); - - hl_context_projection_forward(input.getData(), - reinterpret_cast(sequence.getData()), - weight.getData(), - output.getData(), - sequence.dims_[0] - 1, - input.dims_[1], + size_t begin_pad) { + CHECK(input && output); + hl_context_projection_forward(input->getData(), + sequence.getData(), + weight ? weight->getData() : nullptr, + output->getData(), + sequence.getSize() - 1, + input->getWidth(), context_length, context_start, - begin_pad, - is_padding); + begin_pad); } __global__ void KeContextProjectionBackwardData(real* out_grad, @@ -200,22 +190,17 @@ void hl_context_projection_backward_data(real* out_grad, } template <> -void ContextProjectionBackwardData(Tensor& out_grad, - Tensor& in_grad, - const Tensor& sequence, - size_t context_length, - int context_start) { - CHECK(in_grad.getData() && out_grad.getData() && sequence.getData()); - CHECK_EQ(out_grad.dims_.size(), 2); - CHECK_EQ(in_grad.dims_.size(), 2); - CHECK_EQ(sequence.dims_.size(), 1); - CHECK_EQ(out_grad.dims_[1], in_grad.dims_[1] * context_length); - - hl_context_projection_backward_data(out_grad.getData(), - reinterpret_cast(sequence.getData()), - in_grad.getData(), - sequence.dims_[0] - 1, - in_grad.dims_[1], +void ContextProjectionBackwardData(GpuMatrix* out_grad, + GpuMatrix* in_grad, + const GpuIVector& sequence, + size_t context_length, + int context_start) { + CHECK(in_grad && out_grad); + hl_context_projection_backward_data(out_grad->getData(), + sequence.getData(), + in_grad->getData(), + sequence.getSize() - 1, + in_grad->getWidth(), context_length, context_start); } @@ -320,24 +305,20 @@ void hl_context_projection_backward_weight(real* out_grad, } template <> -void ContextProjectionBackwardWeight(Tensor& out_grad, - Tensor& w_grad, - const Tensor& sequence, - size_t context_length, - int context_start, - size_t total_pad, - size_t begin_pad) { - CHECK(w_grad.getData() && out_grad.getData() && sequence.getData()); - CHECK_EQ(out_grad.dims_.size(), 2); - CHECK_EQ(w_grad.dims_.size(), 2); - CHECK_EQ(sequence.dims_.size(), 1); - CHECK_EQ(out_grad.dims_[1], w_grad.dims_[1] * context_length); - - hl_context_projection_backward_weight(out_grad.getData(), - reinterpret_cast(sequence.getData()), - w_grad.getData(), - sequence.dims_[0] - 1, - w_grad.dims_[1], +void ContextProjectionBackwardWeight( + GpuMatrix* out_grad, + GpuMatrix* w_grad, + const GpuIVector& seq_vec, + size_t context_length, + int context_start, + size_t total_pad, + size_t begin_pad) { + CHECK(out_grad && w_grad); + hl_context_projection_backward_weight(out_grad->getData(), + seq_vec.getData(), + w_grad->getData(), + seq_vec.getSize() - 1, + w_grad->getWidth(), total_pad, context_length, context_start, @@ -345,24 +326,27 @@ void ContextProjectionBackwardWeight(Tensor& out_grad, } template <> -void ContextProjectionBackward(Tensor& out_grad, - Tensor& in_grad, - Tensor& w_grad, - const Tensor& sequence, - size_t context_length, - int context_start, - size_t begin_pad, - bool is_padding, - size_t total_pad) { - if (in_grad.getData()) { - ContextProjectionBackwardData(out_grad, +void ContextProjectionBackward(GpuMatrix* out_grad, + GpuMatrix* in_grad, + GpuMatrix* w_grad, + const GpuIVector& sequence, + size_t context_length, + int context_start, + size_t begin_pad, + bool is_padding, + size_t total_pad) { + CHECK(out_grad); + if (in_grad) { + ContextProjectionBackwardData( + out_grad, in_grad, sequence, context_length, context_start); } - if (is_padding && w_grad.getData()) { - ContextProjectionBackwardWeight(out_grad, + if (is_padding && w_grad) { + ContextProjectionBackwardWeight( + out_grad, w_grad, sequence, context_length, diff --git a/paddle/function/ContextProjectionOpTest.cpp b/paddle/function/ContextProjectionOpTest.cpp index 372fc21cf1..359428fc03 100644 --- a/paddle/function/ContextProjectionOpTest.cpp +++ b/paddle/function/ContextProjectionOpTest.cpp @@ -32,8 +32,7 @@ void testMatrixProjectionForward(int context_start, FuncConfig() .set("context_length", context_length) .set("context_start", context_start) - .set("begin_pad", std::max(0, -context_start)) - .set("is_padding", is_padding)); + .set("begin_pad", std::max(0, -context_start))); CpuMatrix cpu_in(batch_size, input_dim); cpu_in.randomizeUniform(); diff --git a/paddle/function/Function.h b/paddle/function/Function.h index 210eba1301..9e8cbb8e48 100644 --- a/paddle/function/Function.h +++ b/paddle/function/Function.h @@ -40,6 +40,19 @@ struct MatrixT { using type = GpuMatrix; }; +template +struct SequenceT; + +template <> +struct SequenceT { + using type = CpuIVector; +}; + +template <> +struct SequenceT { + using type = GpuIVector; +}; + typedef std::vector Dims; class Tensor { diff --git a/paddle/gserver/layers/ContextProjection.cpp b/paddle/gserver/layers/ContextProjection.cpp index 37e951a1e3..e947b2b9ec 100644 --- a/paddle/gserver/layers/ContextProjection.cpp +++ b/paddle/gserver/layers/ContextProjection.cpp @@ -53,8 +53,7 @@ bool ContextProjection::init() { FuncConfig() .set("context_length", context_length) .set("context_start", context_start) - .set("begin_pad", beginPad_) - .set("is_padding", is_padding)); + .set("begin_pad", beginPad_)); createFunction(backward_, "ContextProjectionBackward", FuncConfig() @@ -112,7 +111,7 @@ void ContextProjection::forward() { size_t dim = out_->value->getWidth(); CHECK_EQ(dim, input_dim * config_.context_length()); size_t batch_size = in_->value->getHeight(); - CHECK_EQ(batch_size, out_->value->getHeight()); + CHECK_EQ(forward_.size(), 1) << "Only one forward function here"; REGISTER_TIMER_INFO("ContextProjectionForward", getName().c_str()); bool is_padding = config_.trainable_padding(); @@ -120,12 +119,6 @@ void ContextProjection::forward() { auto w_ptr = state_ ? state_.get() : is_padding ? weight_->getW().get() : nullptr; auto start_pos = in_->sequenceStartPositions; - /// if use state_ as weight_, w_ptr already has mem, so padding true - forward_[0]->init(FuncConfig() - .set("context_length", config_.context_length()) - .set("context_start", config_.context_start()) - .set("begin_pad", beginPad_) - .set("is_padding", state_ ? true : is_padding)); forward_[0]->calc({Tensor(in_->value->getData(), Dims{batch_size, input_dim}), Tensor(w_ptr ? w_ptr->getData() : nullptr, Dims{w_ptr ? w_ptr->getHeight() : 0, input_dim}), @@ -161,6 +154,7 @@ void ContextProjection::backward(const UpdateCallback& callback) { CHECK_EQ(dim, input_dim * config_.context_length()); size_t batch_size = in_->value->getHeight(); CHECK_EQ(batch_size, out_->value->getHeight()); + CHECK_EQ(backward_.size(), 1) << "Only one backward function here"; REGISTER_TIMER_INFO("ContextProjectionBackward", getName().c_str()); bool is_padding = config_.trainable_padding(); From ec6b13dbfc209a2c574b6064c0a712fa8717d950 Mon Sep 17 00:00:00 2001 From: xutianbing Date: Thu, 29 Dec 2016 16:16:13 -0800 Subject: [PATCH 081/119] clean up unused code. --- paddle/cuda/include/hl_sequence.h | 72 ------ paddle/cuda/include/stub/hl_sequence_stub.h | 29 --- paddle/cuda/src/hl_cuda_sequence.cu | 252 -------------------- paddle/function/ContextProjectionOpGpu.cu | 64 ++++- paddle/math/Matrix.cpp | 169 ------------- paddle/math/Matrix.h | 72 ------ paddle/math/tests/test_matrixCompare.cpp | 142 ----------- 7 files changed, 53 insertions(+), 747 deletions(-) diff --git a/paddle/cuda/include/hl_sequence.h b/paddle/cuda/include/hl_sequence.h index 9bcd25b062..9f9d8f972e 100644 --- a/paddle/cuda/include/hl_sequence.h +++ b/paddle/cuda/include/hl_sequence.h @@ -48,78 +48,6 @@ extern void hl_max_sequence_forward(real* input, extern void hl_max_sequence_backward( real* outputGrad, int* index, real* inputGrad, int numSequences, int dim); -/** - * @brief Context projection forward. - * - * @param[in] input input sequence. - * @param[in] sequence sequence index. - * @param[in] weightData padding data. - * @param[out] output output sequence. - * @param[in] numSequences number of sequences. - * @param[in] inputDim input sequence dimension. - * @param[in] contextLength context length. - * @param[in] contextStart context start. - * @param[in] beginPad number of extra timesteps added at the - * beginning. - * @param[in] isPadding trainable padding. - * - */ -extern void hl_context_projection_forward(real* input, - const int* sequence, - real* weightData, - real* output, - int numSequences, - int inputDim, - int contextLength, - int contextStart, - int beginPad, - bool isPadding); - -/** - * @brief Context projection backward data. - * - * @param[in] outputGrad output gradient. - * @param[in] sequence sequence index. - * @param[out] inputGrad input gradient. - * @param[in] numSequences number of sequences. - * @param[in] inputDim input sequence dimension. - * @param[in] contextLength context length. - * @param[in] contextStart context start. - * - */ -extern void hl_context_projection_backward_data(real* outputGrad, - const int* sequence, - real* inputGrad, - int numSequences, - int inputDim, - int contextLength, - int contextStart); - -/** - * @brief Context projection backward weight. - * - * @param[in] outputGrad output gradient. - * @param[in] sequence sequence index. - * @param[out] weightGrad weight gradient. - * @param[in] numSequences number of sequences. - * @param[in] weightDim input sequence dimension. - * @param[in] totalPad number of extra timesteps. - * @param[in] contextLength context length. - * @param[in] contextStart context start. - * @param[in] beginPad number of extra timesteps added at the - * beginning. - * - */ -extern void hl_context_projection_backward_weight(real* outputGrad, - const int* sequence, - real* weightGrad, - int numSequences, - int weightDim, - int totalPad, - int contextLength, - int contextStart, - int beginPad); - /** * @brief Memory copy from sequence to batch. * diff --git a/paddle/cuda/include/stub/hl_sequence_stub.h b/paddle/cuda/include/stub/hl_sequence_stub.h index d6b07556f8..05e51bce9e 100644 --- a/paddle/cuda/include/stub/hl_sequence_stub.h +++ b/paddle/cuda/include/stub/hl_sequence_stub.h @@ -27,35 +27,6 @@ inline void hl_max_sequence_forward(real* input, inline void hl_max_sequence_backward( real* outputGrad, int* index, real* inputGrad, int numSequences, int dim) {} -inline void hl_context_projection_forward(real* input, - const int* sequence, - real* weightData, - real* output, - int numSequences, - int inputDim, - int contextLength, - int contextStart, - int beginPad, - bool isPadding) {} - -inline void hl_context_projection_backward_data(real* outputGrad, - const int* sequence, - real* inputGrad, - int numSequences, - int inputDim, - int contextLength, - int contextStart) {} - -inline void hl_context_projection_backward_weight(real* outputGrad, - const int* sequence, - real* weightGrad, - int numSequences, - int weightDim, - int totalPad, - int contextLength, - int contextStart, - int beginPad) {} - inline void hl_sequence2batch_copy(real* batch, real* sequence, const int* batchIndex, diff --git a/paddle/cuda/src/hl_cuda_sequence.cu b/paddle/cuda/src/hl_cuda_sequence.cu index 4e33ac443c..ba823de272 100644 --- a/paddle/cuda/src/hl_cuda_sequence.cu +++ b/paddle/cuda/src/hl_cuda_sequence.cu @@ -90,258 +90,6 @@ void hl_max_sequence_backward(real* outputGrad, CHECK_SYNC("hl_max_sequence_backward failed"); } -template -__global__ void KeContextProjectionForward(real* input, - const int* sequence, - real* weightData, - real* output, - int inputDim, - int contextLength, - int contextStart, - int beginPad) { - int idx = threadIdx.x; - int blockSize = blockDim.x; - int sequenceId = blockIdx.x; - int seqStart = sequence[sequenceId]; - int seqEnd = sequence[sequenceId+1]; - real value = 0; - - int instances = seqEnd - seqStart + contextLength - 1; - output += seqStart * inputDim * contextLength; - input += seqStart * inputDim; - for (int k = 0; k <= inputDim / blockSize; k++) { - if (idx < inputDim) { - for (int i = 0; i < instances; i++) { - // i + contextStart; - if ((i + contextStart) < 0) { - if (padding) { - value = weightData[i * inputDim + idx]; - } else { - continue; - } - } else if ((i + contextStart) >= (seqEnd - seqStart)) { - if (padding) { - value = - weightData[(beginPad + i + contextStart - (seqEnd - seqStart)) * - inputDim + idx]; - } else { - continue; - } - } else { - value = input[(i + contextStart) * inputDim + idx]; - } - - int outx = (i - contextLength) < 0 ? i : (contextLength - 1); - int outy = (i - contextLength) < 0 ? 0 : (i - (contextLength - 1)); - real* output_r = - output + outy * inputDim * contextLength + outx * inputDim; - for (int j = outy; j < seqEnd - seqStart; j++) { - output_r[idx] += value; - if (j - outy == outx) break; - output_r += (contextLength - 1) * inputDim; - } - } - } - idx += blockSize; - } -} - -void hl_context_projection_forward(real* input, - const int* sequence, - real* weightData, - real* output, - int numSequences, - int inputDim, - int contextLength, - int contextStart, - int beginPad, - bool isPadding) { - CHECK_NOTNULL(input); - CHECK_NOTNULL(sequence); - CHECK_NOTNULL(output); - CHECK(!isPadding || weightData); - - int blockSize = 128; - int blocksX = numSequences; - int blocksY = 1; - dim3 threads(blockSize, 1); - dim3 grid(blocksX, blocksY); - - if (isPadding) { - KeContextProjectionForward<<< grid, threads, 0, STREAM_DEFAULT >>> - (input, sequence, weightData, output, inputDim, - contextLength, contextStart, beginPad); - } else { - KeContextProjectionForward<<< grid, threads, 0, STREAM_DEFAULT >>> - (input, sequence, weightData, output, inputDim, - contextLength, contextStart, beginPad); - } - CHECK_SYNC("hl_context_projection_forward failed"); -} - -__global__ void KeContextProjectionBackwardData(real* outputGrad, - const int* sequence, - real* inputGrad, - int inputDim, - int contextLength, - int contextStart) { - int idx = threadIdx.x; - int blockSize = blockDim.x; - int sequenceId = blockIdx.x; - int seqStart = sequence[sequenceId]; - int seqEnd = sequence[sequenceId+1]; - real value = 0; - - int instances = seqEnd - seqStart + contextLength - 1; - outputGrad += seqStart * inputDim * contextLength; - inputGrad += seqStart * inputDim; - for (int k = 0; k <= inputDim / blockSize; k++) { - if (idx < inputDim) { - for (int i = 0; i < instances; i++) { - if ((i + contextStart) < 0) { - continue; - } else if ((i + contextStart) >= (seqEnd - seqStart)) { - continue; - } else { - // value = 0; - value = inputGrad[(i + contextStart) * inputDim + idx]; - } - - int outx = (i - contextLength) < 0 ? i : (contextLength - 1); - int outy = (i - contextLength) < 0 ? 0 : (i - (contextLength - 1)); - real* output_r = - outputGrad + outy * inputDim * contextLength + outx * inputDim; - for (int j = outy; j < seqEnd - seqStart; j++) { - value += output_r[idx]; - if (j - outy == outx) break; - output_r += (contextLength - 1) * inputDim; - } - inputGrad[(i + contextStart) * inputDim + idx] = value; - } - } - idx += blockSize; - } -} - -void hl_context_projection_backward_data(real* outputGrad, - const int* sequence, - real* inputGrad, - int numSequences, - int inputDim, - int contextLength, - int contextStart) { - CHECK_NOTNULL(outputGrad); - CHECK_NOTNULL(sequence); - CHECK_NOTNULL(inputGrad); - - int blockSize = 128; - int blocksX = numSequences; - int blocksY = 1; - dim3 threads(blockSize, 1); - dim3 grid(blocksX, blocksY); - KeContextProjectionBackwardData<<< grid, threads, 0, STREAM_DEFAULT >>> - (outputGrad, sequence, inputGrad, inputDim, contextLength, contextStart); - CHECK_SYNC("hl_context_projection_backward_data failed"); -} - -template -__global__ void KeContextProjectionBackwardWeight(real* outputGrad, - const int* sequence, - real* weightGrad, - int numSequences, - int weightDim, - int contextLength, - int contextStart, - int beginPad) { - __shared__ real sum_s[THREADS_Y][THREADS_X]; - int padOfBlock = (weightDim + THREADS_X - 1) / THREADS_X; - const int idx = threadIdx.x; - const int idy = threadIdx.y; - int padId = blockIdx.x / padOfBlock; - int weightIdx = idx + THREADS_X * (blockIdx.x % padOfBlock); - int instanceId; - real value = 0; - real* output_r; - - sum_s[idy][idx] = 0.0f; - if (weightIdx < weightDim) { - for (int seqId = idy; seqId < numSequences; seqId += THREADS_Y) { - int seqStart = sequence[seqId]; - int seqEnd = sequence[seqId+1]; - output_r = outputGrad + seqStart * weightDim * contextLength; - - if (contextStart < 0) { - if (padId + contextStart < 0) { - instanceId = padId; - } else { - // beginPad > 0; - instanceId = (padId - beginPad) + (seqEnd - seqStart) - contextStart; - } - } else { - if (padId + (seqEnd - seqStart) < contextStart) { - continue; - } else { - // beginPad == 0; - instanceId = padId + (seqEnd - seqStart) - contextStart; - } - } - - int outx = (instanceId - contextLength) < 0 ? - instanceId : (contextLength - 1); - int outy = (instanceId - contextLength) < 0 ? - 0 : (instanceId - (contextLength - 1)); - output_r += outy * weightDim * contextLength + outx * weightDim; - for (int j = outy; j < seqEnd - seqStart; j++) { - value += output_r[weightIdx]; - if (j - outy == outx) break; - output_r += (contextLength - 1) * weightDim; - } - } - sum_s[idy][idx] = value; - } - __syncthreads(); - - for (int stride = THREADS_Y/2; stride > 0; stride = stride/2) { - if (idy < stride) { - sum_s[idy][idx] += sum_s[idy + stride][idx]; - } - __syncthreads(); - } - __syncthreads(); - - if (weightIdx < weightDim) { - if (idy == 0) { - weightGrad[padId * weightDim + weightIdx] += sum_s[0][idx]; - } - } -} - -void hl_context_projection_backward_weight(real* outputGrad, - const int* sequence, - real* weightGrad, - int numSequences, - int weightDim, - int totalPad, - int contextLength, - int contextStart, - int beginPad) { - CHECK_NOTNULL(outputGrad); - CHECK_NOTNULL(sequence); - CHECK_NOTNULL(weightGrad); - - int threadsX = 32; - int threadsY = 32; - int blocksX = totalPad * ((weightDim + threadsX - 1) / threadsX); - dim3 threads(threadsX, threadsY); - dim3 grid(blocksX, 1); - - KeContextProjectionBackwardWeight<32, 32> - <<< grid, threads, 0, STREAM_DEFAULT >>> - (outputGrad, sequence, weightGrad, numSequences, weightDim, - contextLength, contextStart, beginPad); - CHECK_SYNC("hl_context_projection_backward_weight failed"); -} - template __global__ void KeMatrixAddRows(real* output, real* table, diff --git a/paddle/function/ContextProjectionOpGpu.cu b/paddle/function/ContextProjectionOpGpu.cu index 7c4ebacdbf..1ec7058f96 100644 --- a/paddle/function/ContextProjectionOpGpu.cu +++ b/paddle/function/ContextProjectionOpGpu.cu @@ -73,15 +73,30 @@ __global__ void KeContextProjectionForward(const real* input, } } +/** + * @brief Context projection forward. + * + * @param[in] input input sequence. + * @param[in] sequence sequence index. + * @param[in] weight padding data. + * @param[out] output output sequence. + * @param[in] num_sequences number of sequences. + * @param[in] input_dim input sequence dimension. + * @param[in] context_length context length. + * @param[in] context_start context start. + * @param[in] begin_pad number of extra timesteps added at the + * beginning. + * + */ void hl_context_projection_forward(const real* input, const int* sequence, const real* weight, real* output, - int num_sequences, - int input_dim, - int context_length, + size_t num_sequences, + size_t input_dim, + size_t context_length, int context_start, - int begin_pad) { + size_t begin_pad) { CHECK_NOTNULL(input); CHECK_NOTNULL(sequence); CHECK_NOTNULL(output); @@ -168,12 +183,24 @@ __global__ void KeContextProjectionBackwardData(real* out_grad, } } +/** + * @brief Context projection backward data. + * + * @param[in] out_grad output gradient. + * @param[in] sequence sequence index. + * @param[out] input_grad input gradient. + * @param[in] num_sequences number of sequences. + * @param[in] input_dim input sequence dimension. + * @param[in] context_length context length. + * @param[in] context_start context start. + * + */ void hl_context_projection_backward_data(real* out_grad, const int* sequence, real* input_grad, - int num_sequences, - int input_dim, - int context_length, + size_t num_sequences, + size_t input_dim, + size_t context_length, int context_start) { CHECK_NOTNULL(out_grad); CHECK_NOTNULL(sequence); @@ -278,15 +305,30 @@ __global__ void KeContextProjectionBackwardWeight(real* out_grad, } } +/** + * @brief Context projection backward weight. + * + * @param[in] out_grad output gradient. + * @param[in] sequence sequence index. + * @param[out] w_grad weight gradient. + * @param[in] num_sequences number of sequences. + * @param[in] w_dim input sequence dimension. + * @param[in] total_pad number of extra timesteps. + * @param[in] context_length context length. + * @param[in] context_start context start. + * @param[in] begin_pad number of extra timesteps added at the + * beginning. + * + */ void hl_context_projection_backward_weight(real* out_grad, const int* sequence, real* w_grad, - int num_sequences, - int w_dim, + size_t num_sequences, + size_t w_dim, size_t total_pad, - int context_length, + size_t context_length, int context_start, - int begin_pad) { + size_t begin_pad) { CHECK_NOTNULL(out_grad); CHECK_NOTNULL(sequence); CHECK_NOTNULL(w_grad); diff --git a/paddle/math/Matrix.cpp b/paddle/math/Matrix.cpp index 50d2e3eb67..90813a8996 100644 --- a/paddle/math/Matrix.cpp +++ b/paddle/math/Matrix.cpp @@ -1304,68 +1304,6 @@ void GpuMatrix::maxSequenceBackward(Matrix& outputGrad, hl_max_sequence_backward(outGrad, maxIndex, inputGrad, numSequences, dim); } -void GpuMatrix::contextProjectionForward(Matrix& input, - Matrix* weight, - const IVector& sequence, - int contextLength, - int contextStart, - size_t beginPad, - bool isPadding) { - CHECK(dynamic_cast(&input)); - CHECK(dynamic_cast(&sequence)); - if (weight) CHECK(dynamic_cast(weight)); - CHECK_EQ(getWidth(), input.getWidth() * contextLength); - - hl_context_projection_forward(input.getData(), - sequence.getData(), - isPadding ? weight->getData() : NULL, - getData(), - sequence.getSize() - 1, - input.getWidth(), - contextLength, - contextStart, - beginPad, - isPadding); -} - -void GpuMatrix::contextProjectionBackwardData(Matrix& inputGrad, - const IVector& sequence, - int contextLength, - int contextStart) { - CHECK(dynamic_cast(&inputGrad)); - CHECK(dynamic_cast(&sequence)); - CHECK_EQ(getWidth(), inputGrad.getWidth() * contextLength); - - hl_context_projection_backward_data(getData(), - sequence.getData(), - inputGrad.getData(), - sequence.getSize() - 1, - inputGrad.getWidth(), - contextLength, - contextStart); -} - -void GpuMatrix::contextProjectionBackwardWeight(Matrix& weightGrad, - const IVector& sequence, - int contextLength, - int contextStart, - int totalPad, - size_t beginPad) { - CHECK(dynamic_cast(&weightGrad)); - CHECK(dynamic_cast(&sequence)); - CHECK_EQ(getWidth(), weightGrad.getWidth() * contextLength); - - hl_context_projection_backward_weight(getData(), - sequence.getData(), - weightGrad.getData(), - sequence.getSize() - 1, - weightGrad.getWidth(), - totalPad, - contextLength, - contextStart, - beginPad); -} - void GpuMatrix::paramReluForward(Matrix& data, Matrix& W) { CHECK(data.useGpu_ == true && W.useGpu_ == true) << "Matrix type are not equal"; @@ -2203,113 +2141,6 @@ void CpuMatrix::maxSequenceBackward(Matrix& outputGrad, } } -void CpuMatrix::contextProjectionForward(Matrix& input, - Matrix* weight, - const IVector& sequence, - int contextLength, - int contextStart, - size_t beginPad, - bool isPadding) { - auto input_ptr = dynamic_cast(&input); - auto seq_ptr = dynamic_cast(&sequence); - CHECK(input_ptr && seq_ptr); - if (weight) CHECK(dynamic_cast(weight)); - CHECK_EQ(getWidth(), input_ptr->getWidth() * contextLength); - - const int* starts = seq_ptr->getData(); - size_t numSequences = seq_ptr->getSize() - 1; - for (size_t i = 0; i < numSequences; ++i) { - for (int j = 0; j < contextLength; ++j) { - int begin = starts[i] + contextStart + j; - int end = starts[i + 1] + contextStart + j; - int dstBegin = starts[i]; - int dstEnd = starts[i + 1]; - if (begin < starts[i]) { - int64_t padSize = - std::min(starts[i] - begin, starts[i + 1] - starts[i]); - MatrixPtr mat = this->subMatrix(starts[i], padSize); - if (isPadding) { - MatrixPtr sub = weight->subMatrix(j, padSize); - mat->addAtOffset(*sub, j * input_ptr->getWidth()); - } - dstBegin = starts[i] + padSize; - begin = starts[i]; - } - if (end > starts[i + 1]) { - int64_t padSize = - std::min(end - starts[i + 1], starts[i + 1] - starts[i]); - MatrixPtr mat = this->subMatrix(starts[i + 1] - padSize, padSize); - if (isPadding) { - MatrixPtr sub = - weight->subMatrix(beginPad + contextStart + j - padSize, padSize); - mat->addAtOffset(*sub, j * input_ptr->getWidth()); - } - dstEnd = starts[i + 1] - padSize; - end = starts[i + 1]; - } - if (end <= begin) continue; - MatrixPtr src = input_ptr->subMatrix(begin, end - begin); - MatrixPtr dst = this->subMatrix(dstBegin, dstEnd - dstBegin); - dst->addAtOffset(*src, j * input_ptr->getWidth()); - } - } -} - -void CpuMatrix::contextProjectionBackward(Matrix* inputGrad, - Matrix* weightGrad, - const IVector& sequence, - int contextLength, - int contextStart, - size_t beginPad, - bool isPadding) { - if (inputGrad) CHECK(dynamic_cast(inputGrad)); - if (weightGrad) CHECK(dynamic_cast(weightGrad)); - CHECK(dynamic_cast(&sequence)); - - int64_t inputDim = inputGrad ? inputGrad->getWidth() - : weightGrad ? weightGrad->getWidth() : 0; - CHECK_EQ(getWidth(), inputDim * contextLength); - - const int* starts = sequence.getData(); - size_t numSequences = sequence.getSize() - 1; - for (size_t i = 0; i < numSequences; ++i) { - for (int j = 0; j < contextLength; ++j) { - int begin = starts[i] + contextStart + j; - int end = starts[i + 1] + contextStart + j; - int dstBegin = starts[i]; - int dstEnd = starts[i + 1]; - if (begin < starts[i]) { - int64_t padSize = - std::min(starts[i] - begin, starts[i + 1] - starts[i]); - if (isPadding && weightGrad) { - MatrixPtr mat = this->subMatrix(starts[i], padSize); - MatrixPtr sub = weightGrad->subMatrix(j, padSize); - sub->addAtOffset(*mat, j * inputDim); - } - dstBegin = starts[i] + padSize; - begin = starts[i]; - } - if (end > starts[i + 1]) { - int64_t padSize = - std::min(end - starts[i + 1], starts[i + 1] - starts[i]); - if (isPadding && weightGrad) { - MatrixPtr mat = this->subMatrix(starts[i + 1] - padSize, padSize); - MatrixPtr sub = weightGrad->subMatrix( - beginPad + contextStart + j - padSize, padSize); - sub->addAtOffset(*mat, j * inputDim); - } - dstEnd = starts[i + 1] - padSize; - end = starts[i + 1]; - } - if (end <= begin) continue; - if (!inputGrad) continue; - MatrixPtr src = inputGrad->subMatrix(begin, end - begin); - MatrixPtr dst = this->subMatrix(dstBegin, dstEnd - dstBegin); - src->addAtOffset(*dst, j * inputDim); - } - } -} - inline void vecAddTo(real* a, const real* b, size_t len) { for (unsigned int i = 0; i < len; ++i) { a[i] += b[i]; diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index bda863de38..4865a081a5 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -972,42 +972,6 @@ public: LOG(FATAL) << "Not implemeted"; } - virtual void contextProjectionForward(Matrix& input, - Matrix* weight, - const IVector& sequence, - int contextLength, - int contextStart, - size_t beginPad, - bool isPadding) { - LOG(FATAL) << "Not implemeted"; - } - - virtual void contextProjectionBackward(Matrix* inputGrad, - Matrix* weightGrad, - const IVector& sequence, - int contextLength, - int contextStart, - size_t beginPad, - bool isPadding) { - LOG(FATAL) << "Not implemeted"; - } - - virtual void contextProjectionBackwardData(Matrix& inputGrad, - const IVector& sequence, - int contextLength, - int contextStart) { - LOG(FATAL) << "Not implemeted"; - } - - virtual void contextProjectionBackwardWeight(Matrix& weightGrad, - const IVector& sequence, - int contextLength, - int contextStart, - int totalPad, - size_t beginPad) { - LOG(FATAL) << "Not implemeted"; - } - /** * @code * this.row[i] += table.row[ids[i]] @@ -1442,26 +1406,6 @@ public: const IVector& sequence, IVector& index); - void contextProjectionForward(Matrix& input, - Matrix* weight, - const IVector& sequence, - int contextLength, - int contextStart, - size_t beginPad, - bool isPadding); - - void contextProjectionBackwardData(Matrix& inputGrad, - const IVector& sequence, - int contextLength, - int contextStart); - - void contextProjectionBackwardWeight(Matrix& weightGrad, - const IVector& sequence, - int contextLength, - int contextStart, - int totalPad, - size_t beginPad); - void bilinearForward(const Matrix& in, const size_t inImgH, const size_t inImgW, @@ -1648,22 +1592,6 @@ public: const IVector& sequence, IVector& index); - void contextProjectionForward(Matrix& input, - Matrix* weight, - const IVector& sequence, - int contextLength, - int contextStart, - size_t beginPad, - bool isPadding); - - void contextProjectionBackward(Matrix* inputGrad, - Matrix* weightGrad, - const IVector& sequence, - int contextLength, - int contextStart, - size_t beginPad, - bool isPadding); - real* getRow(size_t row) { return BaseMatrix::rowBuf(row); } virtual real* getRowBuf(size_t row) { return getRow(row); } diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index efda4ff27b..98d63438a5 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -29,148 +29,6 @@ using namespace std; // NOLINT using autotest::TensorCheckEqual; using autotest::TensorCheckErr; -void testMatrixProjectionForward(int contextStart, - int contextLength, - bool padding, - int batchSize, - int inputDim) { - MatrixPtr cpuInput = std::make_shared(batchSize, inputDim); - MatrixPtr gpuInput = std::make_shared(batchSize, inputDim); - cpuInput->randomizeUniform(); - gpuInput->copyFrom(*cpuInput); - - int pad = std::max(0, -contextStart) + - std::max(0, contextStart + contextLength - 1); - if (pad == 0) padding = false; - MatrixPtr cpuWeight = nullptr; - MatrixPtr gpuWeight = nullptr; - if (padding) { - cpuWeight = std::make_shared(pad, inputDim); - gpuWeight = std::make_shared(pad, inputDim); - cpuWeight->randomizeUniform(); - gpuWeight->copyFrom(*cpuWeight); - } - - IVectorPtr cpuSequence; - generateSequenceStartPositions(batchSize, cpuSequence); - IVectorPtr gpuSequence = IVector::create(cpuSequence->getSize(), true); - gpuSequence->copyFrom(*cpuSequence); - - MatrixPtr cpuOutput = - std::make_shared(batchSize, inputDim * contextLength); - MatrixPtr gpuOutput = - std::make_shared(batchSize, inputDim * contextLength); - cpuOutput->randomizeUniform(); - gpuOutput->copyFrom(*cpuOutput); - - // calculate - int beginPad = std::max(0, -contextStart); - cpuOutput->contextProjectionForward(*cpuInput, - cpuWeight.get(), - *cpuSequence, - contextLength, - contextStart, - beginPad, - padding); - - gpuOutput->contextProjectionForward(*gpuInput, - gpuWeight.get(), - *gpuSequence, - contextLength, - contextStart, - beginPad, - padding); - - TensorCheckEqual(*cpuOutput, *gpuOutput); -} - -void testMatrixProjectionBackward(int contextStart, - int contextLength, - bool padding, - int batchSize, - int inputDim) { - MatrixPtr cpuOutputGrad = - std::make_shared(batchSize, inputDim * contextLength); - MatrixPtr gpuOutputGrad = - std::make_shared(batchSize, inputDim * contextLength); - cpuOutputGrad->randomizeUniform(); - gpuOutputGrad->copyFrom(*cpuOutputGrad); - - IVectorPtr cpuSequence; - generateSequenceStartPositions(batchSize, cpuSequence); - IVectorPtr gpuSequence = IVector::create(cpuSequence->getSize(), true); - gpuSequence->copyFrom(*cpuSequence); - - MatrixPtr cpuInputGrad = std::make_shared(batchSize, inputDim); - MatrixPtr gpuInputGrad = std::make_shared(batchSize, inputDim); - cpuInputGrad->randomizeUniform(); - gpuInputGrad->copyFrom(*cpuInputGrad); - - int pad = std::max(0, -contextStart) + - std::max(0, contextStart + contextLength - 1); - if (pad == 0) padding = false; - MatrixPtr cpuWeightGrad = nullptr; - MatrixPtr gpuWeightGrad = nullptr; - if (padding) { - cpuWeightGrad = std::make_shared(pad, inputDim); - gpuWeightGrad = std::make_shared(pad, inputDim); - cpuWeightGrad->randomizeUniform(); - gpuWeightGrad->copyFrom(*cpuWeightGrad); - } - - // calculate - int beginPad = std::max(0, -contextStart); - cpuOutputGrad->contextProjectionBackward(cpuInputGrad.get(), - cpuWeightGrad.get(), - *cpuSequence, - contextLength, - contextStart, - beginPad, - padding); - gpuOutputGrad->contextProjectionBackwardData( - *gpuInputGrad, *gpuSequence, contextLength, contextStart); - if (padding) { - gpuOutputGrad->contextProjectionBackwardWeight(*gpuWeightGrad, - *gpuSequence, - contextLength, - contextStart, - pad, - beginPad); - } - - TensorCheckErr(*cpuInputGrad, *gpuInputGrad); - if (padding) { - TensorCheckErr(*cpuWeightGrad, *gpuWeightGrad); - } -} - -TEST(Matrix, projection) { - for (auto contextStart : {-5, -3, -1, 0, 3}) { - for (auto contextLength : {1, 2, 5, 7}) { - for (auto trainablePadding : {false, true}) { - for (auto batchSize : {1, 2, 5, 20, 100}) { - for (auto inputDim : {15, 32, 63, 128, 200}) { - VLOG(3) << " contextStart=" << contextStart - << " contextLength=" << contextLength - << " trainablePadding=" << trainablePadding - << " batchSize=" << batchSize << " inputDim=" << inputDim; - testMatrixProjectionForward(contextStart, - contextLength, - trainablePadding, - batchSize, - inputDim); - testMatrixProjectionBackward(contextStart, - contextLength, - trainablePadding, - batchSize, - inputDim); - } - } - } - } - } -} - void testMatrixMaxSequence(int batchSize, int inputDim) { // forward MatrixPtr cpuInput = std::make_shared(batchSize, inputDim); From 9dfff7698f2347602b4a6ca9feb32d3226bfb2f7 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 30 Dec 2016 10:39:38 +0800 Subject: [PATCH 082/119] Also check baidu.dict md5 --- demo/model_zoo/embedding/pre_DictAndModel.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/demo/model_zoo/embedding/pre_DictAndModel.sh b/demo/model_zoo/embedding/pre_DictAndModel.sh index 7ad3aa05e9..f61c65a935 100755 --- a/demo/model_zoo/embedding/pre_DictAndModel.sh +++ b/demo/model_zoo/embedding/pre_DictAndModel.sh @@ -16,10 +16,9 @@ set -e set -x BASE_URL='http://paddlepaddle.cdn.bcebos.com/model_zoo/embedding' -wget ${BASE_URL}/baidu.dict - -DOWNLOAD_ITEMS=(model_32.emb model_64.emb model_128.emb model_256.emb) -ITEM_MD5=(f88c8325ee6da6187f1080e8fe66c1cd +DOWNLOAD_ITEMS=(baidu.dict model_32.emb model_64.emb model_128.emb model_256.emb) +ITEM_MD5=(fa03a12321eaab6c30a8fcc9442eaea3 + f88c8325ee6da6187f1080e8fe66c1cd 927cf70f27f860aff1a5703ebf7f1584 a52e43655cd25d279777ed509a1ae27b b92c67fe9ff70fea53596080e351ac80) From ace405b78bed3121d2fb81c48504848b850e07f4 Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Fri, 30 Dec 2016 13:11:53 +0800 Subject: [PATCH 083/119] refine print information --- paddle/utils/ThreadLocal.cpp | 2 +- paddle/utils/Util.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/paddle/utils/ThreadLocal.cpp b/paddle/utils/ThreadLocal.cpp index d27dae33fd..58fe51bd40 100644 --- a/paddle/utils/ThreadLocal.cpp +++ b/paddle/utils/ThreadLocal.cpp @@ -37,7 +37,7 @@ unsigned int* ThreadLocalRand::getSeed() { p = new unsigned int(defaultSeed_ - 1); } else { p = new unsigned int(defaultSeed_ + getTID()); - LOG(INFO) << "thread use undeterministic rand seed:" << *p; + VLOG(3) << "thread use undeterministic rand seed:" << *p; } seed_.set(p); } diff --git a/paddle/utils/Util.cpp b/paddle/utils/Util.cpp index 0f778dbebf..411a64aa8d 100644 --- a/paddle/utils/Util.cpp +++ b/paddle/utils/Util.cpp @@ -125,7 +125,7 @@ void registerInitFunction(std::function func, int priority) { void runInitFunctions() { std::call_once(g_onceFlag, []() { - LOG(INFO) << "Calling runInitFunctions"; + VLOG(3) << "Calling runInitFunctions"; if (g_initFuncs) { std::sort(g_initFuncs->begin(), g_initFuncs->end(), @@ -139,7 +139,7 @@ void runInitFunctions() { g_initFuncs = nullptr; } g_initialized = true; - LOG(INFO) << "Call runInitFunctions done."; + VLOG(3) << "Call runInitFunctions done."; }); } @@ -231,7 +231,7 @@ std::string join(const std::string& part1, const std::string& part2) { } // namespace path void copyFileToPath(const std::string& file, const std::string& dir) { - LOG(INFO) << "copy " << file << " to " << dir; + VLOG(3) << "copy " << file << " to " << dir; std::string fileName = path::basename(file); std::string dst = path::join(dir, fileName); std::ifstream source(file, std::ios_base::binary); From 68c89bcc522f8dfac53302f06547872bc92597f8 Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Mon, 26 Dec 2016 21:24:10 +0800 Subject: [PATCH 084/119] Add jupyter notebook support in Docker images. --- paddle/scripts/docker/Dockerfile | 13 +++++++++++-- paddle/scripts/docker/Dockerfile.gpu | 13 +++++++++++-- paddle/scripts/docker/build.sh | 4 +++- paddle/scripts/docker/run_all | 8 ++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100755 paddle/scripts/docker/run_all diff --git a/paddle/scripts/docker/Dockerfile b/paddle/scripts/docker/Dockerfile index b01de499bd..46363b05b7 100644 --- a/paddle/scripts/docker/Dockerfile +++ b/paddle/scripts/docker/Dockerfile @@ -15,7 +15,7 @@ RUN apt-get update \ && apt-get clean -y RUN cd /usr/src/gtest && cmake . && make && cp *.a /usr/lib RUN pip install -U BeautifulSoup docopt PyYAML pillow \ - sphinx sphinx_rtd_theme recommonmark + sphinx sphinx_rtd_theme recommonmark jupyter ARG WITH_AVX ARG WITH_DOC @@ -43,4 +43,13 @@ RUN echo 'root:root' | chpasswd RUN sed -ri 's/^PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config EXPOSE 22 -CMD ["/usr/sbin/sshd", "-D"] + +# Jupyter Notebook directory. +RUN mkdir /notes/ +WORKDIR "/notes" +EXPOSE 8888 + +RUN mkdir -p /opt/bin +COPY ./paddle/scripts/docker/run_all /opt/bin/ + +CMD ["/opt/bin/run_all"] diff --git a/paddle/scripts/docker/Dockerfile.gpu b/paddle/scripts/docker/Dockerfile.gpu index a68cc79b84..072c144818 100644 --- a/paddle/scripts/docker/Dockerfile.gpu +++ b/paddle/scripts/docker/Dockerfile.gpu @@ -15,7 +15,7 @@ RUN apt-get update \ && apt-get clean -y RUN cd /usr/src/gtest && cmake . && make && cp *.a /usr/lib RUN pip install -U BeautifulSoup docopt PyYAML pillow \ - sphinx sphinx_rtd_theme recommonmark + sphinx sphinx_rtd_theme recommonmark jupyter ARG WITH_AVX ARG WITH_DOC @@ -43,4 +43,13 @@ RUN echo 'root:root' | chpasswd RUN sed -ri 's/^PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config EXPOSE 22 -CMD ["/usr/sbin/sshd", "-D"] + +# Jupyter Notebook directory. +RUN mkdir /notes/ +WORKDIR "/notes" +EXPOSE 8888 + +RUN mkdir -p /opt/bin +COPY ./paddle/scripts/docker/run_all /opt/bin/ + +CMD ["/opt/bin/run_all"] diff --git a/paddle/scripts/docker/build.sh b/paddle/scripts/docker/build.sh index ca3f1c3f18..7edba3dd09 100755 --- a/paddle/scripts/docker/build.sh +++ b/paddle/scripts/docker/build.sh @@ -43,5 +43,7 @@ cp -rv /woboq/data $WOBOQ_OUT/../data -o $WOBOQ_OUT \ -p paddle:/paddle /woboq/indexgenerator/codebrowser_indexgenerator $WOBOQ_OUT - +cd /woboq +make clean +rm -rf /paddle/build trap : 0 diff --git a/paddle/scripts/docker/run_all b/paddle/scripts/docker/run_all new file mode 100755 index 0000000000..87083467f5 --- /dev/null +++ b/paddle/scripts/docker/run_all @@ -0,0 +1,8 @@ +#!/bin/bash +LOG=/var/log/all + +touch $LOG + +/usr/sbin/sshd -D >> $LOG & +jupyter notebook --ip=0.0.0.0 /notes/ >> $LOG & +tail -f $LOG From e0a81dca263df428deeca10dee7783346d64411b Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Tue, 3 Jan 2017 19:42:51 +0800 Subject: [PATCH 085/119] add translated chinese docs into catalog --- doc/howto/deep_model/rnn/index_cn.rst | 1 + doc/howto/deep_model/rnn/rnn_cn.md | 226 ------------------ .../deep_model/{ => rnn}/rnn_config_cn.rst | 59 +++-- doc/howto/index_cn.rst | 5 +- doc/howto/index_en.rst | 4 +- doc/howto/usage/cmd_parameter/index_cn.rst | 11 + doc/howto/usage/cmd_parameter/index_en.md | 8 - doc/howto/usage/cmd_parameter/index_en.rst | 11 + .../k8s-aws/README.md => k8s/k8s_aws_en.md} | 8 +- doc/howto/usage/{cluster => }/k8s/k8s_cn.md | 2 +- .../{cluster => }/k8s/k8s_distributed_cn.md | 4 +- doc/howto/usage/{cluster => }/k8s/k8s_en.md | 0 .../usage/{cluster/k8s => k8s/src}/Dockerfile | 0 .../src}/add_security_group.png | Bin .../k8s-aws => k8s/src}/create_efs.png | Bin .../k8s-aws => k8s/src}/efs_mount.png | Bin .../usage/{cluster/k8s => k8s/src}/job.yaml | 0 .../k8s => k8s/src}/k8s-paddle-arch.png | Bin .../k8s-aws => k8s/src}/managed_policy.png | Bin .../usage/{cluster/k8s => k8s/src}/start.sh | 0 .../{cluster/k8s => k8s/src}/start_paddle.py | 0 doc/tutorials/index_cn.md | 2 + doc/tutorials/index_en.md | 1 + 23 files changed, 67 insertions(+), 275 deletions(-) delete mode 100644 doc/howto/deep_model/rnn/rnn_cn.md rename doc/howto/deep_model/{ => rnn}/rnn_config_cn.rst (88%) create mode 100644 doc/howto/usage/cmd_parameter/index_cn.rst delete mode 100644 doc/howto/usage/cmd_parameter/index_en.md create mode 100644 doc/howto/usage/cmd_parameter/index_en.rst rename doc/howto/usage/{cluster/k8s-aws/README.md => k8s/k8s_aws_en.md} (99%) rename doc/howto/usage/{cluster => }/k8s/k8s_cn.md (99%) rename doc/howto/usage/{cluster => }/k8s/k8s_distributed_cn.md (99%) rename doc/howto/usage/{cluster => }/k8s/k8s_en.md (100%) rename doc/howto/usage/{cluster/k8s => k8s/src}/Dockerfile (100%) rename doc/howto/usage/{cluster/k8s-aws => k8s/src}/add_security_group.png (100%) rename doc/howto/usage/{cluster/k8s-aws => k8s/src}/create_efs.png (100%) rename doc/howto/usage/{cluster/k8s-aws => k8s/src}/efs_mount.png (100%) rename doc/howto/usage/{cluster/k8s => k8s/src}/job.yaml (100%) rename doc/howto/usage/{cluster/k8s => k8s/src}/k8s-paddle-arch.png (100%) rename doc/howto/usage/{cluster/k8s-aws => k8s/src}/managed_policy.png (100%) rename doc/howto/usage/{cluster/k8s => k8s/src}/start.sh (100%) rename doc/howto/usage/{cluster/k8s => k8s/src}/start_paddle.py (100%) diff --git a/doc/howto/deep_model/rnn/index_cn.rst b/doc/howto/deep_model/rnn/index_cn.rst index 9e805ca851..9ecab5594c 100644 --- a/doc/howto/deep_model/rnn/index_cn.rst +++ b/doc/howto/deep_model/rnn/index_cn.rst @@ -4,6 +4,7 @@ RNN相关模型 .. toctree:: :maxdepth: 1 + rnn_config_cn.rst recurrent_group_cn.md hierarchical_layer_cn.rst hrnn_rnn_api_compare_cn.rst diff --git a/doc/howto/deep_model/rnn/rnn_cn.md b/doc/howto/deep_model/rnn/rnn_cn.md deleted file mode 100644 index 5ec05b2cab..0000000000 --- a/doc/howto/deep_model/rnn/rnn_cn.md +++ /dev/null @@ -1,226 +0,0 @@ -RNN 配置 -================= - -本教程将指导你如何在 PaddlePaddle 中配置循环神经网络(RNN)。PaddlePaddle 高度支持灵活和高效的循环神经网络配置。 在本教程中,您将了解如何: - -- 准备用来学习循环神经网络的序列数据。 -- 配置循环神经网络架构。 -- 使用学习完成的循环神经网络模型生成序列。 - -我们将使用 vanilla 循环神经网络和 sequence to sequence 模型来指导你完成这些步骤。sequence to sequence 模型的代码可以在`demo / seqToseq`找到。 - -准备序列数据 ---------------------- - -PaddlePaddle 不需要对序列数据进行任何预处理,例如填充。唯一需要做的是将相应类型设置为输入。例如,以下代码段定义了三个输入。 它们都是序列,它们的大小是`src_dict`,`trg_dict`和`trg_dict`: - -``` sourceCode -settings.input_types = [ - integer_value_sequence(len(settings.src_dict)), - integer_value_sequence(len(settings.trg_dict)), - integer_value_sequence(len(settings.trg_dict))] -``` - -在`process`函数中,每个`yield`函数将返回三个整数列表。每个整数列表被视为一个整数序列: - -``` sourceCode -yield src_ids, trg_ids, trg_ids_next -``` - -有关如何编写数据提供程序的更多细节描述,请参考 [PyDataProvider2](../../ui/data_provider/index.html)。完整的数据提供文件在 `demo/seqToseq/dataprovider.py`。 - -配置循环神经网络架构 ------------------------------------------------ - -### 简单门控循环神经网络(Gated Recurrent Neural Network) - -循环神经网络在每个时间步骤顺序地处理序列。下面列出了 LSTM 的架构的示例。 - -![image](../../../tutorials/sentiment_analysis/bi_lstm.jpg) - -一般来说,循环网络从 *t* = 1 到 *t* = *T* 或者反向地从 *t* = *T* 到 *t* = 1 执行以下操作。 - -*x**t* + 1 = *f**x*(*x**t*),*y**t* = *f**y*(*x**t*) - -其中 *f**x*(.) 称为**单步函数**(即单时间步执行的函数,step function),而 *f**y*(.) 称为**输出函数**。在 vanilla 循环神经网络中,单步函数和输出函数都非常简单。然而,PaddlePaddle 可以通过修改这两个函数来实现复杂的网络配置。我们将使用 sequence to sequence 模型演示如何配置复杂的循环神经网络模型。在本节中,我们将使用简单的 vanilla 循环神经网络作为使用`recurrent_group`配置简单循环神经网络的例子。 注意,如果你只需要使用简单的RNN,GRU或LSTM,那么推荐使用`grumemory`和`lstmemory`,因为它们的计算效率比`recurrent_group`更高。 - -对于 vanilla RNN,在每个时间步长,**单步函数**为: - -*x**t* + 1 = *W**x**x**t* + *W**i**I**t* + *b* - -其中 *x**t* 是RNN状态,并且 *I**t* 是输入,*W**x* 和 *W**i* 分别是RNN状态和输入的变换矩阵。*b* 是偏差。它的**输出函数**只需要*x**t*作为输出。 - -`recurrent_group`是构建循环神经网络的最重要的工具。 它定义了**单步函数**,**输出函数**和循环神经网络的输入。注意,这个函数的`step`参数需要实现`step function`(单步函数)和`output function`(输出函数): - - -``` sourceCode -def simple_rnn(input, - size=None, - name=None, - reverse=False, - rnn_bias_attr=None, - act=None, - rnn_layer_attr=None): - def __rnn_step__(ipt): - out_mem = memory(name=name, size=size) - rnn_out = mixed_layer(input = [full_matrix_projection(ipt), - full_matrix_projection(out_mem)], - name = name, - bias_attr = rnn_bias_attr, - act = act, - layer_attr = rnn_layer_attr, - size = size) - return rnn_out - return recurrent_group(name='%s_recurrent_group' % name, - step=__rnn_step__, - reverse=reverse, - input=input) -``` - -PaddlePaddle 使用“Memory”(记忆模块)实现单步函数。**Memory**是在PaddlePaddle中构造循环神经网络时最重要的概念。 Memory是在单步函数中循环使用的状态,例如*x**t* + 1 = *f**x*(*x**t*)。 一个Memory包含**输出**和**输入**。当前时间步处的Memory的输出作为下一时间步Memory的输入。Memory也可以具有**boot layer(引导层)**,其输出被用作Memory的初始值。 在我们的例子中,门控循环单元的输出被用作输出Memory。请注意,`rnn_out`层的名称与`out_mem`的名称相同。这意味着`rnn_out` (*x**t* + 1)的输出被用作`out_mem`Memory的**输出**。 - -Memory也可以是序列。在这种情况下,在每个时间步中,我们有一个序列作为循环神经网络的状态。这在构造非常复杂的循环神经网络时是有用的。 其他高级功能包括定义多个Memory,以及使用子序列来定义分级循环神经网络架构。 - -我们在函数的结尾返回`rnn_out`。 这意味着 `rnn_out` 层的输出被用作门控循环神经网络的**输出**函数。 - -### Sequence to Sequence Model with Attention - -我们将使用 sequence to sequence model with attention 作为例子演示如何配置复杂的循环神经网络模型。该模型的说明如下图所示。 - -![image](../../../tutorials/text_generation/encoder-decoder-attention-model.png) - -在这个模型中,源序列 *S* = {*s*1, …, *s**T*} 用双向门控循环神经网络编码。双向门控循环神经网络的隐藏状态 *H**S* = {*H*1, …, *H**T*} 被称为 *编码向量*。解码器是门控循环神经网络。当解读每一个*y**t*时, 这个门控循环神经网络生成一系列权重 *W**S**t* = {*W*1*t*, …, *W**T**t*}, 用于计算编码向量的加权和。加权和用来生成*y**t*。 - -模型的编码器部分如下所示。它叫做`grumemory`来表示门控循环神经网络。如果网络架构简单,那么推荐使用循环神经网络的方法,因为它比 `recurrent_group` 更快。我们已经实现了大多数常用的循环神经网络架构,可以参考 [Layers](../../ui/api/trainer_config_helpers/layers_index.html) 了解更多细节。 - -我们还将编码向量投射到 `decoder_size` 维空间。这通过获得反向循环网络的第一个实例,并将其投射到 `decoder_size` 维空间完成: - -``` sourceCode -# 定义源语句的数据层 -src_word_id = data_layer(name='source_language_word', size=source_dict_dim) -# 计算每个词的词向量 -src_embedding = embedding_layer( - input=src_word_id, - size=word_vector_dim, - param_attr=ParamAttr(name='_source_language_embedding')) -# 应用前向循环神经网络 -src_forward = grumemory(input=src_embedding, size=encoder_size) -# 应用反向递归神经网络(reverse=True表示反向循环神经网络) -src_backward = grumemory(input=src_embedding, - size=encoder_size, - reverse=True) -# 将循环神经网络的前向和反向部分混合在一起 -encoded_vector = concat_layer(input=[src_forward, src_backward]) - -# 投射编码向量到 decoder_size -encoder_proj = mixed_layer(input = [full_matrix_projection(encoded_vector)], - size = decoder_size) - -# 计算反向RNN的第一个实例 -backward_first = first_seq(input=src_backward) - -# 投射反向RNN的第一个实例到 decoder size -decoder_boot = mixed_layer(input=[full_matrix_projection(backward_first)], size=decoder_size, act=TanhActivation()) -``` - -解码器使用 `recurrent_group` 来定义循环神经网络。单步函数和输出函数在 `gru_decoder_with_attention` 中定义: - -``` sourceCode -group_inputs=[StaticInput(input=encoded_vector,is_seq=True), - StaticInput(input=encoded_proj,is_seq=True)] -trg_embedding = embedding_layer( - input=data_layer(name='target_language_word', - size=target_dict_dim), - size=word_vector_dim, - param_attr=ParamAttr(name='_target_language_embedding')) -group_inputs.append(trg_embedding) - -# 对于配备有注意力机制的解码器,在训练中, -# 目标向量(groudtruth)是数据输入, -# 而源序列的编码向量可以被无边界的memory访问 -# StaticInput 意味着不同时间步的输入都是相同的值, -# 否则它以一个序列输入,不同时间步的输入是不同的。 -# 所有输入序列应该有相同的长度。 -decoder = recurrent_group(name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs) -``` - -单步函数的实现如下所示。首先,它定义解码网络的**Memory**。然后定义 attention,门控循环单元单步函数和输出函数: - -``` sourceCode -def gru_decoder_with_attention(enc_vec, enc_proj, current_word): - # 定义解码器的Memory - # Memory的输出定义在 gru_step 内 - # 注意 gru_step 应该与它的Memory名字相同 - decoder_mem = memory(name='gru_decoder', - size=decoder_size, - boot_layer=decoder_boot) - # 计算 attention 加权编码向量 - context = simple_attention(encoded_sequence=enc_vec, - encoded_proj=enc_proj, - decoder_state=decoder_mem) - # 混合当前词向量和attention加权编码向量 - decoder_inputs = mixed_layer(inputs = [full_matrix_projection(context), - full_matrix_projection(current_word)], - size = decoder_size * 3) - # 定义门控循环单元循环神经网络单步函数 - gru_step = gru_step_layer(name='gru_decoder', - input=decoder_inputs, - output_mem=decoder_mem, - size=decoder_size) - # 定义输出函数 - out = mixed_layer(input=[full_matrix_projection(input=gru_step)], - size=target_dict_dim, - bias_attr=True, - act=SoftmaxActivation()) - return out -``` - -生成序列 ------------------ - -训练模型后,我们可以使用它来生成序列。通常的做法是使用**beam search** 生成序列。以下代码片段定义 beam search 算法。注意,`beam_search` 函数假设 `step` 的输出函数返回的是下一个时刻输出词的 softmax 归一化概率向量。我们对模型进行了以下更改。 - -- 使用 `GeneratedInput` 来表示 trg\_embedding。 `GeneratedInput` 将上一时间步所生成的词的向量来作为当前时间步的输入。 -- 使用 `beam_search` 函数。这个函数需要设置: - - `bos_id`: 开始标记。每个句子都以开始标记开头。 - - `eos_id`: 结束标记。每个句子都以结束标记结尾。 - - `beam_size`: beam search 算法中的beam大小。 - - `max_length`: 生成序列的最大长度。 -- 使用 `seqtext_printer_evaluator` 根据索引矩阵和字典打印文本。这个函数需要设置: - - `id_input`: 数据的整数ID,用于标识生成的文件中的相应输出。 - - `dict_file`: 用于将词ID转换为词的字典文件。 - - `result_file`: 生成结果文件的路径。 - -代码如下: - -``` sourceCode -group_inputs=[StaticInput(input=encoded_vector,is_seq=True), - StaticInput(input=encoded_proj,is_seq=True)] -# 在生成时,解码器基于编码源序列和最后生成的目标词预测下一目标词。 -# 编码源序列(编码器输出)必须由只读Memory的 StaticInput 指定。 -# 这里, GeneratedInputs 自动获取上一个生成的词,并在最开始初始化为起始词,如 。 -trg_embedding = GeneratedInput( - size=target_dict_dim, - embedding_name='_target_language_embedding', - embedding_size=word_vector_dim) -group_inputs.append(trg_embedding) -beam_gen = beam_search(name=decoder_group_name, - step=gru_decoder_with_attention, - input=group_inputs, - bos_id=0, # Beginnning token. - eos_id=1, # End of sentence token. - beam_size=beam_size, - max_length=max_length) - -seqtext_printer_evaluator(input=beam_gen, - id_input=data_layer(name="sent_id", size=1), - dict_file=trg_dict_path, - result_file=gen_trans_file) -outputs(beam_gen) -``` - -注意,这种生成技术只用于类似解码器的生成过程。如果你正在处理序列标记任务,请参阅 [Semantic Role Labeling Demo](../../demo/semantic_role_labeling/index.html) 了解更多详细信息。 - -完整的配置文件在`demo/seqToseq/seqToseq_net.py`。 diff --git a/doc/howto/deep_model/rnn_config_cn.rst b/doc/howto/deep_model/rnn/rnn_config_cn.rst similarity index 88% rename from doc/howto/deep_model/rnn_config_cn.rst rename to doc/howto/deep_model/rnn/rnn_config_cn.rst index e6d8c1133a..8d65b3512d 100644 --- a/doc/howto/deep_model/rnn_config_cn.rst +++ b/doc/howto/deep_model/rnn/rnn_config_cn.rst @@ -1,4 +1,4 @@ -RNN 配置 +RNN配置 ======== 本教程将指导你如何在 PaddlePaddle @@ -20,7 +20,7 @@ PaddlePaddle 不需要对序列数据进行任何预处理,例如填充。唯一需要做的是将相应类型设置为输入。例如,以下代码段定义了三个输入。 它们都是序列,它们的大小是\ ``src_dict``\ ,\ ``trg_dict``\ 和\ ``trg_dict``\ : -.. code:: sourcecode +.. code:: python settings.input_types = [ integer_value_sequence(len(settings.src_dict)), @@ -29,7 +29,7 @@ PaddlePaddle 在\ ``process``\ 函数中,每个\ ``yield``\ 函数将返回三个整数列表。每个整数列表被视为一个整数序列: -.. code:: sourcecode +.. code:: python yield src_ids, trg_ids, trg_ids_next @@ -45,18 +45,17 @@ PaddlePaddle 循环神经网络在每个时间步骤顺序地处理序列。下面列出了 LSTM 的架构的示例。 -.. figure:: ../../../tutorials/sentiment_analysis/bi_lstm.jpg - :alt: image +.. image:: ../../../tutorials/sentiment_analysis/bi_lstm.jpg + :align: center - image +一般来说,循环网络从 :math:`t=1` 到 :math:`t=T` 或者反向地从 :math:`t=T` 到 :math:`t=1` 执行以下操作。 -一般来说,循环网络从 *t* = 1 到 *t* = *T* 或者反向地从 *t* = *T* 到 *t* -= 1 执行以下操作。 +.. math:: -*x*\ \ *t* + 1 = *f*\ \ *x*\ (*x*\ \ *t*\ ),\ *y*\ \ *t*\  = *f*\ \ *y*\ (*x*\ \ *t*\ ) + x_{t+1} = f_x(x_t), y_t = f_y(x_t) -其中 *f*\ \ *x*\ (.) 称为\ **单步函数**\ (即单时间步执行的函数,step -function),而 *f*\ \ *y*\ (.) 称为\ **输出函数**\ 。在 vanilla +其中 :math:`f_x(.)` 称为\ **单步函数**\ (即单时间步执行的函数,step +function),而 :math:`f_y(.)` 称为\ **输出函数**\ 。在 vanilla 循环神经网络中,单步函数和输出函数都非常简单。然而,PaddlePaddle 可以通过修改这两个函数来实现复杂的网络配置。我们将使用 sequence to sequence @@ -67,16 +66,17 @@ vanilla 对于 vanilla RNN,在每个时间步长,\ **单步函数**\ 为: -*x*\ \ *t* + 1 = *W*\ \ *x*\ \ *x*\ \ *t*\  + *W*\ \ *i*\ \ *I*\ \ *t*\  + *b* +.. math:: -其中 *x*\ \ *t*\ 是RNN状态,并且 *I*\ \ *t*\ 是输入,\ *W*\ \ *x*\ 和 -*W*\ \ *i*\ 分别是RNN状态和输入的变换矩阵。\ *b* -是偏差。它的\ **输出函数**\ 只需要\ *x*\ \ *t*\ 作为输出。 + x_{t+1} = W_x x_t + W_i I_t + b + +其中 :math:`x_t` 是RNN状态,并且 :math:`I_t` 是输入,:math:`W_x` 和 +:math:`W_i` 分别是RNN状态和输入的变换矩阵。:math:`b` 是偏差。它的\ **输出函数**\ 只需要 :math:`x_t` 作为输出。 ``recurrent_group``\ 是构建循环神经网络的最重要的工具。 它定义了\ **单步函数**\ ,\ **输出函数**\ 和循环神经网络的输入。注意,这个函数的\ ``step``\ 参数需要实现\ ``step function``\ (单步函数)和\ ``output function``\ (输出函数): -.. code:: sourcecode +.. code:: python def simple_rnn(input, size=None, @@ -102,7 +102,7 @@ vanilla PaddlePaddle 使用“Memory”(记忆模块)实现单步函数。\ **Memory**\ 是在PaddlePaddle中构造循环神经网络时最重要的概念。 -Memory是在单步函数中循环使用的状态,例如\ *x*\ \ *t* + 1 = *f*\ \ *x*\ (*x*\ \ *t*\ )。 +Memory是在单步函数中循环使用的状态,例如 :math:`x_{t+1} = f_x(x_t)` 。 一个Memory包含\ **输出**\ 和\ **输入**\ 。当前时间步处的Memory的输出作为下一时间步Memory的输入。Memory也可以具有\ **boot layer(引导层)**\ ,其输出被用作Memory的初始值。 在我们的例子中,门控循环单元的输出被用作输出Memory。请注意,\ ``rnn_out``\ 层的名称与\ ``out_mem``\ 的名称相同。这意味着\ ``rnn_out`` @@ -120,18 +120,15 @@ Sequence to Sequence Model with Attention 我们将使用 sequence to sequence model with attention 作为例子演示如何配置复杂的循环神经网络模型。该模型的说明如下图所示。 -.. figure:: ../../../tutorials/text_generation/encoder-decoder-attention-model.png - :alt: image - - image +.. image:: ../../../tutorials/text_generation/encoder-decoder-attention-model.png + :align: center -在这个模型中,源序列 *S* = {*s*\ 1, …, \ *s*\ \ *T*\ } +在这个模型中,源序列 :math:`S = \{s_1, \dots, s_T\}` 用双向门控循环神经网络编码。双向门控循环神经网络的隐藏状态 -*H*\ \ *S*\  = {*H*\ 1, …, \ *H*\ \ *T*\ } 被称为 -*编码向量*\ 。解码器是门控循环神经网络。当解读每一个\ *y*\ \ *t*\ 时, -这个门控循环神经网络生成一系列权重 -*W*\ \ *S*\ \ *t*\  = {*W*\ 1\ *t*\ , …, \ *W*\ \ *T*\ \ *t*\ }, -用于计算编码向量的加权和。加权和用来生成\ *y*\ \ *t*\ 。 +:math:`H_S = \{H_1, \dots, H_T\}` 被称为 +*编码向量*\ 。解码器是门控循环神经网络。当解读每一个 :math:`y_t` 时, +这个门控循环神经网络生成一系列权重 :math:`W_S^t = \{W_1^t, \dots, W_T^t\}` , +用于计算编码向量的加权和。加权和用来生成 :math:`y_t` 。 模型的编码器部分如下所示。它叫做\ ``grumemory``\ 来表示门控循环神经网络。如果网络架构简单,那么推荐使用循环神经网络的方法,因为它比 ``recurrent_group`` @@ -143,7 +140,7 @@ Sequence to Sequence Model with Attention 维空间。这通过获得反向循环网络的第一个实例,并将其投射到 ``decoder_size`` 维空间完成: -.. code:: sourcecode +.. code:: python # 定义源语句的数据层 src_word_id = data_layer(name='source_language_word', size=source_dict_dim) @@ -174,7 +171,7 @@ Sequence to Sequence Model with Attention 解码器使用 ``recurrent_group`` 来定义循环神经网络。单步函数和输出函数在 ``gru_decoder_with_attention`` 中定义: -.. code:: sourcecode +.. code:: python group_inputs=[StaticInput(input=encoded_vector,is_seq=True), StaticInput(input=encoded_proj,is_seq=True)] @@ -198,7 +195,7 @@ Sequence to Sequence Model with Attention 单步函数的实现如下所示。首先,它定义解码网络的\ **Memory**\ 。然后定义 attention,门控循环单元单步函数和输出函数: -.. code:: sourcecode +.. code:: python def gru_decoder_with_attention(enc_vec, enc_proj, current_word): # 定义解码器的Memory @@ -253,7 +250,7 @@ attention,门控循环单元单步函数和输出函数: 代码如下: -.. code:: sourcecode +.. code:: python group_inputs=[StaticInput(input=encoded_vector,is_seq=True), StaticInput(input=encoded_proj,is_seq=True)] diff --git a/doc/howto/index_cn.rst b/doc/howto/index_cn.rst index 6a14ce8ae7..bd3d0ec292 100644 --- a/doc/howto/index_cn.rst +++ b/doc/howto/index_cn.rst @@ -7,10 +7,11 @@ .. toctree:: :maxdepth: 1 + usage/cmd_parameter/index_cn.rst usage/concepts/use_concepts_cn.rst usage/cluster/cluster_train_cn.md - usage/cluster/k8s/k8s_cn.md - usage/cluster/k8s/k8s_distributed_cn.md + usage/k8s/k8s_cn.md + usage/k8s/k8s_distributed_cn.md 开发标准 -------- diff --git a/doc/howto/index_en.rst b/doc/howto/index_en.rst index 983dc743eb..1fbfcd260b 100644 --- a/doc/howto/index_en.rst +++ b/doc/howto/index_en.rst @@ -7,8 +7,10 @@ Usage .. toctree:: :maxdepth: 1 - usage/cmd_parameter/index_en.md + usage/cmd_parameter/index_en.rst usage/cluster/cluster_train_en.md + usage/k8s/k8s_en.md + usage/k8s/k8s_aws_en.md Development ------------ diff --git a/doc/howto/usage/cmd_parameter/index_cn.rst b/doc/howto/usage/cmd_parameter/index_cn.rst new file mode 100644 index 0000000000..4c87298211 --- /dev/null +++ b/doc/howto/usage/cmd_parameter/index_cn.rst @@ -0,0 +1,11 @@ +.. _cmd_line_index: + +设置命令行参数 +=============== + +.. toctree:: + :maxdepth: 1 + + use_case_cn.md + arguments_cn.md + detail_introduction_cn.md diff --git a/doc/howto/usage/cmd_parameter/index_en.md b/doc/howto/usage/cmd_parameter/index_en.md deleted file mode 100644 index 2a96e7e976..0000000000 --- a/doc/howto/usage/cmd_parameter/index_en.md +++ /dev/null @@ -1,8 +0,0 @@ -```eval_rst -.. _cmd_line_index: -``` -# Set Command-line Parameters - -* [Use Case](use_case_en.md) -* [Arguments](arguments_en.md) -* [Detailed Descriptions](detail_introduction_en.md) diff --git a/doc/howto/usage/cmd_parameter/index_en.rst b/doc/howto/usage/cmd_parameter/index_en.rst new file mode 100644 index 0000000000..0e3c72d27a --- /dev/null +++ b/doc/howto/usage/cmd_parameter/index_en.rst @@ -0,0 +1,11 @@ +.. _cmd_line_index: + +Set Command-line Parameters +=========================== + +.. toctree:: + :maxdepth: 1 + + use_case_en.md + arguments_en.md + detail_introduction_en.md diff --git a/doc/howto/usage/cluster/k8s-aws/README.md b/doc/howto/usage/k8s/k8s_aws_en.md similarity index 99% rename from doc/howto/usage/cluster/k8s-aws/README.md rename to doc/howto/usage/k8s/k8s_aws_en.md index 5931584288..201bcae48d 100644 --- a/doc/howto/usage/cluster/k8s-aws/README.md +++ b/doc/howto/usage/k8s/k8s_aws_en.md @@ -1,4 +1,4 @@ -# PaddlePaddle on AWS with Kubernetes +# Kubernetes on AWS ## Create AWS Account and IAM Account @@ -331,15 +331,15 @@ For sharing the training data across all the Kubernetes nodes, we use EFS (Elast 1. Make sure you added AmazonElasticFileSystemFullAccess policy in your group. 1. Create the Elastic File System in AWS console, and attach the new VPC with it. - + 1. Modify the Kubernetes security group under ec2/Security Groups, add additional inbound policy "All TCP TCP 0 - 65535 0.0.0.0/0" for Kubernetes default VPC security group. - + 1. Follow the EC2 mount instruction to mount the disk onto all the Kubernetes nodes, we recommend to mount EFS disk onto ~/efs. - + Before starting the training, you should place your user config and divided training data onto EFS. When the training start, each task will copy related files from EFS into container, and it will also write the training results back onto EFS, we will show you how to place the data later in this article. diff --git a/doc/howto/usage/cluster/k8s/k8s_cn.md b/doc/howto/usage/k8s/k8s_cn.md similarity index 99% rename from doc/howto/usage/cluster/k8s/k8s_cn.md rename to doc/howto/usage/k8s/k8s_cn.md index 2575701053..ab07cb9cd5 100644 --- a/doc/howto/usage/cluster/k8s/k8s_cn.md +++ b/doc/howto/usage/k8s/k8s_cn.md @@ -1,4 +1,4 @@ -# Kubernetes 单机训练 +# Kubernetes单机训练 在这篇文档里,我们介绍如何在 Kubernetes 集群上启动一个单机使用CPU的Paddle训练作业。在下一篇中,我们将介绍如何启动分布式训练作业。 diff --git a/doc/howto/usage/cluster/k8s/k8s_distributed_cn.md b/doc/howto/usage/k8s/k8s_distributed_cn.md similarity index 99% rename from doc/howto/usage/cluster/k8s/k8s_distributed_cn.md rename to doc/howto/usage/k8s/k8s_distributed_cn.md index 53d0b4676c..b63b8437a0 100644 --- a/doc/howto/usage/cluster/k8s/k8s_distributed_cn.md +++ b/doc/howto/usage/k8s/k8s_distributed_cn.md @@ -1,4 +1,4 @@ -# Kubernetes 分布式训练 +# Kubernetes分布式训练 前一篇文章介绍了如何在Kubernetes集群上启动一个单机PaddlePaddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上进行分布式PaddlePaddle训练作业。关于PaddlePaddle的分布式训练,文章 [Cluster Training](https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md)介绍了一种通过SSH远程分发任务,进行分布式训练的方法,与此不同的是,本文将介绍在Kubernetes容器管理平台上快速构建PaddlePaddle容器集群,进行分布式训练的方案。 @@ -22,7 +22,7 @@ 首先,我们需要拥有一个Kubernetes集群,在这个集群中所有node与pod都可以互相通信。关于Kubernetes集群搭建,可以参考[官方文档](http://kubernetes.io/docs/getting-started-guides/kubeadm/),在以后的文章中我们也会介绍AWS上搭建的方案。本文假设大家能找到几台物理机,并且可以按照官方文档在上面部署Kubernetes。在本文的环境中,Kubernetes集群中所有node都挂载了一个[MFS](http://moosefs.org/)(Moose filesystem,一种分布式文件系统)共享目录,我们通过这个目录来存放训练文件与最终输出的模型。关于MFS的安装部署,可以参考[MooseFS documentation](https://moosefs.com/documentation.html)。在训练之前,用户将配置与训练数据切分好放在MFS目录中,训练时,程序从此目录拷贝文件到容器内进行训练,将结果保存到此目录里。整体的结构图如下: -![paddle on kubernetes结构图](k8s-paddle-arch.png) +![paddle on kubernetes结构图](src/k8s-paddle-arch.png) 上图描述了一个3节点的分布式训练场景,Kubernetes集群的每个node上都挂载了一个MFS目录,这个目录可以通过volume的形式挂载到容器中。Kubernetes为这次训练创建了3个pod并且调度到了3个node上运行,每个pod包含一个PaddlePaddle容器。在容器创建后,会启动pserver与trainer进程,读取volume中的数据进行这次分布式训练。 diff --git a/doc/howto/usage/cluster/k8s/k8s_en.md b/doc/howto/usage/k8s/k8s_en.md similarity index 100% rename from doc/howto/usage/cluster/k8s/k8s_en.md rename to doc/howto/usage/k8s/k8s_en.md diff --git a/doc/howto/usage/cluster/k8s/Dockerfile b/doc/howto/usage/k8s/src/Dockerfile similarity index 100% rename from doc/howto/usage/cluster/k8s/Dockerfile rename to doc/howto/usage/k8s/src/Dockerfile diff --git a/doc/howto/usage/cluster/k8s-aws/add_security_group.png b/doc/howto/usage/k8s/src/add_security_group.png similarity index 100% rename from doc/howto/usage/cluster/k8s-aws/add_security_group.png rename to doc/howto/usage/k8s/src/add_security_group.png diff --git a/doc/howto/usage/cluster/k8s-aws/create_efs.png b/doc/howto/usage/k8s/src/create_efs.png similarity index 100% rename from doc/howto/usage/cluster/k8s-aws/create_efs.png rename to doc/howto/usage/k8s/src/create_efs.png diff --git a/doc/howto/usage/cluster/k8s-aws/efs_mount.png b/doc/howto/usage/k8s/src/efs_mount.png similarity index 100% rename from doc/howto/usage/cluster/k8s-aws/efs_mount.png rename to doc/howto/usage/k8s/src/efs_mount.png diff --git a/doc/howto/usage/cluster/k8s/job.yaml b/doc/howto/usage/k8s/src/job.yaml similarity index 100% rename from doc/howto/usage/cluster/k8s/job.yaml rename to doc/howto/usage/k8s/src/job.yaml diff --git a/doc/howto/usage/cluster/k8s/k8s-paddle-arch.png b/doc/howto/usage/k8s/src/k8s-paddle-arch.png similarity index 100% rename from doc/howto/usage/cluster/k8s/k8s-paddle-arch.png rename to doc/howto/usage/k8s/src/k8s-paddle-arch.png diff --git a/doc/howto/usage/cluster/k8s-aws/managed_policy.png b/doc/howto/usage/k8s/src/managed_policy.png similarity index 100% rename from doc/howto/usage/cluster/k8s-aws/managed_policy.png rename to doc/howto/usage/k8s/src/managed_policy.png diff --git a/doc/howto/usage/cluster/k8s/start.sh b/doc/howto/usage/k8s/src/start.sh similarity index 100% rename from doc/howto/usage/cluster/k8s/start.sh rename to doc/howto/usage/k8s/src/start.sh diff --git a/doc/howto/usage/cluster/k8s/start_paddle.py b/doc/howto/usage/k8s/src/start_paddle.py similarity index 100% rename from doc/howto/usage/cluster/k8s/start_paddle.py rename to doc/howto/usage/k8s/src/start_paddle.py diff --git a/doc/tutorials/index_cn.md b/doc/tutorials/index_cn.md index 97014d5376..6a27004d58 100644 --- a/doc/tutorials/index_cn.md +++ b/doc/tutorials/index_cn.md @@ -2,6 +2,7 @@ * [快速入门](quick_start/index_cn.rst) * [个性化推荐](rec/ml_regression_cn.rst) +* [图像分类](image_classification/index_cn.md) * [情感分析](sentiment_analysis/index_cn.md) * [语义角色标注](semantic_role_labeling/index_cn.md) * [机器翻译](text_generation/index_cn.md) @@ -9,3 +10,4 @@ ## 常用模型 * [ResNet模型](imagenet_model/resnet_model_cn.md) +* [词向量模型](embedding_model/index_cn.md) diff --git a/doc/tutorials/index_en.md b/doc/tutorials/index_en.md index cce9d3a176..77331a703b 100644 --- a/doc/tutorials/index_en.md +++ b/doc/tutorials/index_en.md @@ -7,6 +7,7 @@ There are several examples and demos here. * [Sentiment Analysis](sentiment_analysis/index_en.md) * [Semantic Role Labeling](semantic_role_labeling/index_en.md) * [Text Generation](text_generation/index_en.md) +* [Image Auto-Generation](gan/index_en.md) ## Model Zoo * [ImageNet: ResNet](imagenet_model/resnet_model_en.md) From 936b0ed11863d92dc323b4c0430d6c1dc5e2bfd4 Mon Sep 17 00:00:00 2001 From: xutianbing Date: Tue, 3 Jan 2017 12:33:08 -0800 Subject: [PATCH 086/119] add paddle_test_util static lib to simplify unit test. --- cmake/util.cmake | 1 + paddle/function/CMakeLists.txt | 4 +- paddle/function/ContextProjectionOpTest.cpp | 2 +- paddle/gserver/tests/CMakeLists.txt | 37 +-- paddle/gserver/tests/LayerGradUtil.h | 2 +- paddle/gserver/tests/TestUtil.cpp | 219 ------------------ paddle/gserver/tests/TestUtil.h | 78 ------- paddle/gserver/tests/test_ActivationGrad.cpp | 2 +- paddle/gserver/tests/test_BatchNorm.cpp | 2 +- paddle/gserver/tests/test_ConvTrans.cpp | 2 +- paddle/gserver/tests/test_ConvUnify.cpp | 2 +- paddle/gserver/tests/test_Evaluator.cpp | 2 +- paddle/gserver/tests/test_LayerGrad.cpp | 2 +- paddle/gserver/tests/test_NetworkCompare.cpp | 2 +- paddle/gserver/tests/test_PriorBox.cpp | 2 +- .../gserver/tests/test_ProtoDataProvider.cpp | 2 +- paddle/gserver/tests/test_PyDataProvider.cpp | 2 +- paddle/gserver/tests/test_RecurrentLayer.cpp | 2 +- paddle/gserver/tests/test_WarpCTCLayer.cpp | 2 +- paddle/math/tests/CMakeLists.txt | 3 +- paddle/math/tests/test_GpuProfiler.cpp | 2 +- paddle/math/tests/test_matrixCompare.cpp | 2 +- paddle/testing/CMakeLists.txt | 2 + 23 files changed, 33 insertions(+), 343 deletions(-) delete mode 100644 paddle/gserver/tests/TestUtil.cpp delete mode 100644 paddle/gserver/tests/TestUtil.h diff --git a/cmake/util.cmake b/cmake/util.cmake index 43a56378df..38299a87e9 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -156,6 +156,7 @@ function(link_paddle_test TARGET_NAME) link_paddle_exe(${TARGET_NAME}) target_link_libraries(${TARGET_NAME} paddle_test_main + paddle_test_util ${GTEST_LIBRARIES}) endfunction() diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index 0b3126155d..42a9bd470c 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -17,9 +17,7 @@ if(WITH_TESTING) # file(GLOB test_files . *OpTest.cpp) # add_executable(${test_bin} EXCLUDE_FROM_ALL ${test_files}) add_simple_unittest(CrossMapNormalOpTest) - add_unittest(ContextProjectionOpTest - ContextProjectionOpTest.cpp - ../gserver/tests/TestUtil.cpp) + add_simple_unittest(ContextProjectionOpTest) endif() endif() diff --git a/paddle/function/ContextProjectionOpTest.cpp b/paddle/function/ContextProjectionOpTest.cpp index 359428fc03..6223d2fd23 100644 --- a/paddle/function/ContextProjectionOpTest.cpp +++ b/paddle/function/ContextProjectionOpTest.cpp @@ -14,8 +14,8 @@ limitations under the License. */ #include #include "FunctionTest.h" -#include "paddle/gserver/tests/TestUtil.h" #include "paddle/math/Matrix.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT diff --git a/paddle/gserver/tests/CMakeLists.txt b/paddle/gserver/tests/CMakeLists.txt index c26a2a7f06..6775563b2b 100644 --- a/paddle/gserver/tests/CMakeLists.txt +++ b/paddle/gserver/tests/CMakeLists.txt @@ -2,8 +2,7 @@ ################### test_ProtoDataProvider ############ add_unittest_without_exec(test_ProtoDataProvider - test_ProtoDataProvider.cpp - TestUtil.cpp) + test_ProtoDataProvider.cpp) # test_ProtoDataProvider will mkdir as same name, # so if WORKING_DIRECTORY is default directory, then @@ -15,53 +14,46 @@ add_test(NAME test_ProtoDataProvider ################# test_LayerGrad ####################### add_unittest_without_exec(test_LayerGrad test_LayerGrad.cpp - LayerGradUtil.cpp - TestUtil.cpp) + LayerGradUtil.cpp) add_test(NAME test_LayerGrad COMMAND test_LayerGrad) add_unittest_without_exec(test_ActivationGrad test_ActivationGrad.cpp - LayerGradUtil.cpp - TestUtil.cpp) + LayerGradUtil.cpp) add_test(NAME test_ActivationGrad COMMAND test_ActivationGrad) ################# test_ConvTrans ####################### add_unittest_without_exec(test_ConvTrans test_ConvTrans.cpp - LayerGradUtil.cpp - TestUtil.cpp) + LayerGradUtil.cpp) add_test(NAME test_ConvTrans COMMAND test_ConvTrans) ################# test_PriorBox ####################### add_unittest_without_exec(test_PriorBox test_PriorBox.cpp - LayerGradUtil.cpp - TestUtil.cpp) + LayerGradUtil.cpp) add_test(NAME test_PriorBox COMMAND test_PriorBox) ################# test_ConvUnify ####################### add_unittest_without_exec(test_ConvUnify test_ConvUnify.cpp - LayerGradUtil.cpp - TestUtil.cpp) + LayerGradUtil.cpp) add_test(NAME test_ConvUnify COMMAND test_ConvUnify) ################# test_BatchNorm ####################### add_unittest_without_exec(test_BatchNorm test_BatchNorm.cpp - LayerGradUtil.cpp - TestUtil.cpp) + LayerGradUtil.cpp) add_test(NAME test_BatchNorm COMMAND test_BatchNorm) ################## test_Evaluator ####################### add_unittest(test_Evaluator - test_Evaluator.cpp - TestUtil.cpp) + test_Evaluator.cpp) ################ test_LinearChainCRF #################### add_simple_unittest(test_LinearChainCRF) @@ -72,8 +64,7 @@ add_simple_unittest(test_MultinomialSampler) ############## test_PyDataProvider ######################## if(WITH_PYTHON) add_unittest_without_exec(test_PyDataProvider - test_PyDataProvider.cpp - TestUtil.cpp) + test_PyDataProvider.cpp) add_test(NAME test_PyDataProvider COMMAND .set_python_path.sh -d ./gserver/tests:${PROJ_ROOT}/python/ ${CMAKE_CURRENT_BINARY_DIR}/test_PyDataProvider @@ -81,15 +72,12 @@ if(WITH_PYTHON) endif() ############### test_RecurrentLayer ####################### -add_unittest(test_RecurrentLayer - test_RecurrentLayer.cpp - TestUtil.cpp) +add_simple_unittest(test_RecurrentLayer) ############### test_WarpCTCLayer ####################### if(NOT WITH_DOUBLE) add_unittest_without_exec(test_WarpCTCLayer - test_WarpCTCLayer.cpp - TestUtil.cpp) + test_WarpCTCLayer.cpp) add_test(NAME test_WarpCTCLayer COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_WarpCTCLayer --warpctc_dir=${PROJ_ROOT}/warp-ctc/build @@ -108,8 +96,7 @@ add_test(NAME test_RecurrentGradientMachine WORKING_DIRECTORY ${PROJ_ROOT}/paddle) add_unittest_without_exec(test_NetworkCompare - test_NetworkCompare.cpp - TestUtil.cpp) + test_NetworkCompare.cpp) if(WITH_GPU) add_test(NAME test_NetworkCompare COMMAND .set_python_path.sh -d ${PROJ_ROOT}/python ${CMAKE_CURRENT_BINARY_DIR}/test_NetworkCompare --use_gpu=true diff --git a/paddle/gserver/tests/LayerGradUtil.h b/paddle/gserver/tests/LayerGradUtil.h index 4e88ac0e81..9f68eb64d0 100644 --- a/paddle/gserver/tests/LayerGradUtil.h +++ b/paddle/gserver/tests/LayerGradUtil.h @@ -17,7 +17,7 @@ limitations under the License. */ #include "paddle/gserver/layers/DataLayer.h" #include "paddle/trainer/Trainer.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace std; // NOLINT namespace paddle { diff --git a/paddle/gserver/tests/TestUtil.cpp b/paddle/gserver/tests/TestUtil.cpp deleted file mode 100644 index c691fe2625..0000000000 --- a/paddle/gserver/tests/TestUtil.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "TestUtil.h" -#include -#include "paddle/math/SparseMatrix.h" - -DEFINE_int32(fixed_seq_length, 0, "Produce some sequence of fixed length"); - -namespace paddle { - -std::string randStr(const int len) { - std::string str = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - std::string s = ""; - for (int i = 0; i < len; ++i) s += str[(rand() % 62)]; // NOLINT - return s; -} - -MatrixPtr makeRandomSparseMatrix(size_t height, - size_t width, - bool withValue, - bool useGpu, - bool equalNnzPerSample) { - std::vector ids(height); - std::vector indices(height + 1); - indices[0] = 0; - - std::function randomer = [] { return uniformRandom(10); }; - if (equalNnzPerSample) { - size_t n = 0; - do { - n = uniformRandom(10); - } while (!n); - randomer = [=] { return n; }; - } - for (size_t i = 0; i < height; ++i) { - indices[i + 1] = indices[i] + std::min(randomer(), width); - ids[i] = i; - } - - if (!withValue) { - std::vector data; - data.resize(indices[height] - indices[0]); - for (size_t i = 0; i < data.size(); ++i) { - data[i].col = uniformRandom(width); - } - auto mat = Matrix::createSparseMatrix( - height, width, data.size(), NO_VALUE, SPARSE_CSR, false, useGpu); - if (useGpu) { - std::dynamic_pointer_cast(mat)->copyFrom( - ids.data(), indices.data(), data.data(), HPPL_STREAM_DEFAULT); - } else { - std::dynamic_pointer_cast(mat)->copyFrom( - ids.data(), indices.data(), data.data()); - } - return mat; - } else { - std::vector data; - data.resize(indices[height] - indices[0]); - for (size_t i = 0; i < data.size(); ++i) { - data[i].col = uniformRandom(width); - data[i].value = rand() / static_cast(RAND_MAX); // NOLINT - } - auto mat = Matrix::createSparseMatrix( - height, width, data.size(), FLOAT_VALUE, SPARSE_CSR, false, useGpu); - if (useGpu) { - std::dynamic_pointer_cast(mat)->copyFrom( - ids.data(), indices.data(), data.data(), HPPL_STREAM_DEFAULT); - } else { - std::dynamic_pointer_cast(mat)->copyFrom( - ids.data(), indices.data(), data.data()); - } - return mat; - } -} - -void generateSequenceStartPositions(size_t batchSize, - IVectorPtr& sequenceStartPositions) { - ICpuGpuVectorPtr gpuCpuVec; - generateSequenceStartPositions(batchSize, gpuCpuVec); - sequenceStartPositions = gpuCpuVec->getMutableVector(false); -} - -void generateSequenceStartPositions(size_t batchSize, - ICpuGpuVectorPtr& sequenceStartPositions) { - int numSeqs; - if (FLAGS_fixed_seq_length != 0) { - numSeqs = std::ceil((float)batchSize / (float)FLAGS_fixed_seq_length); - } else { - numSeqs = batchSize / 10 + 1; - } - sequenceStartPositions = - ICpuGpuVector::create(numSeqs + 1, /* useGpu= */ false); - int* buf = sequenceStartPositions->getMutableData(false); - int64_t pos = 0; - int len = FLAGS_fixed_seq_length; - int maxLen = 2 * batchSize / numSeqs; - for (int i = 0; i < numSeqs; ++i) { - if (FLAGS_fixed_seq_length == 0) { - len = uniformRandom( - std::min(maxLen, batchSize - pos - numSeqs + i)) + - 1; - } - buf[i] = pos; - pos += len; - VLOG(1) << " len=" << len; - } - buf[numSeqs] = batchSize; -} - -void generateSubSequenceStartPositions( - const ICpuGpuVectorPtr& sequenceStartPositions, - ICpuGpuVectorPtr& subSequenceStartPositions) { - int numSeqs = sequenceStartPositions->getSize() - 1; - const int* buf = sequenceStartPositions->getData(false); - int numOnes = 0; - for (int i = 0; i < numSeqs; ++i) { - if (buf[i + 1] - buf[i] == 1) { - ++numOnes; - } - } - // each seq has two sub-seq except length 1 - int numSubSeqs = numSeqs * 2 - numOnes; - subSequenceStartPositions = - ICpuGpuVector::create(numSubSeqs + 1, /* useGpu= */ false); - int* subBuf = subSequenceStartPositions->getMutableData(false); - int j = 0; - for (int i = 0; i < numSeqs; ++i) { - if (buf[i + 1] - buf[i] == 1) { - subBuf[j++] = buf[i]; - } else { - int len = uniformRandom(buf[i + 1] - buf[i] - 1) + 1; - subBuf[j++] = buf[i]; - subBuf[j++] = buf[i] + len; - } - } - subBuf[j] = buf[numSeqs]; -} - -void generateMDimSequenceData(const IVectorPtr& sequenceStartPositions, - IVectorPtr& cpuSequenceDims) { - /* generate sequences with 2 dims */ - int numSeqs = sequenceStartPositions->getSize() - 1; - int numDims = 2; - - cpuSequenceDims = IVector::create(numSeqs * numDims, /* useGpu= */ false); - int* bufStarts = sequenceStartPositions->getData(); - int* bufDims = cpuSequenceDims->getData(); - - for (int i = 0; i < numSeqs; i++) { - int len = bufStarts[i + 1] - bufStarts[i]; - /* get width and height randomly */ - std::vector dimVec; - for (int j = 0; j < len; j++) { - if (len % (j + 1) == 0) { - dimVec.push_back(1); - } - } - int idx = rand() % dimVec.size(); // NOLINT use rand_r - bufDims[i * numDims] = dimVec[idx]; - bufDims[i * numDims + 1] = len / dimVec[idx]; - } -} - -void generateMDimSequenceData(const ICpuGpuVectorPtr& sequenceStartPositions, - IVectorPtr& cpuSequenceDims) { - /* generate sequences with 2 dims */ - int numSeqs = sequenceStartPositions->getSize() - 1; - int numDims = 2; - - cpuSequenceDims = IVector::create(numSeqs * numDims, /* useGpu= */ false); - const int* bufStarts = sequenceStartPositions->getData(false); - int* bufDims = cpuSequenceDims->getData(); - - for (int i = 0; i < numSeqs; i++) { - int len = bufStarts[i + 1] - bufStarts[i]; - /* get width and height randomly */ - std::vector dimVec; - for (int j = 0; j < len; j++) { - if (len % (j + 1) == 0) { - dimVec.push_back(1); - } - } - int idx = rand() % dimVec.size(); // NOLINT use rand_r - bufDims[i * numDims] = dimVec[idx]; - bufDims[i * numDims + 1] = len / dimVec[idx]; - } -} - -void checkMatrixEqual(const MatrixPtr& a, const MatrixPtr& b) { - EXPECT_EQ(a->getWidth(), b->getWidth()); - EXPECT_EQ(a->getHeight(), b->getHeight()); - EXPECT_EQ(a->isTransposed(), b->isTransposed()); - for (size_t r = 0; r < a->getHeight(); ++r) { - for (size_t c = 0; c < a->getWidth(); ++c) { - EXPECT_FLOAT_EQ(a->getElement(r, c), b->getElement(r, c)); - } - } -} - -void checkVectorEqual(const IVectorPtr& a, const IVectorPtr& b) { - EXPECT_EQ(a->getSize(), b->getSize()); - for (size_t r = 0; r < a->getSize(); ++r) { - EXPECT_FLOAT_EQ(a->get(r), b->get(r)); - } -} -} // namespace paddle diff --git a/paddle/gserver/tests/TestUtil.h b/paddle/gserver/tests/TestUtil.h deleted file mode 100644 index ec86469aeb..0000000000 --- a/paddle/gserver/tests/TestUtil.h +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include "paddle/math/Matrix.h" - -namespace paddle { - -std::string randStr(const int len); - -inline int uniformRandom(int n) { return n == 0 ? 0 : rand() % n; } - -inline bool approximatelyEqual(float a, float b, float epsilon) { - return fabs(a - b) <= ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); -} - -MatrixPtr makeRandomSparseMatrix(size_t height, - size_t width, - bool withValue, - bool useGpu, - bool equalNnzPerSample = false); - -/** - * @brief generate sequenceStartPositions for INPUT_SEQUENCE_DATA, - * INPUT_HASSUB_SEQUENCE_DATA and INPUT_SEQUENCE_LABEL - * - * @param batchSize batchSize - * sequenceStartPositions[out] generation output - */ -void generateSequenceStartPositions(size_t batchSize, - IVectorPtr& sequenceStartPositions); - -void generateSequenceStartPositions(size_t batchSize, - ICpuGpuVectorPtr& sequenceStartPositions); - -/** - * @brief generate subSequenceStartPositions for INPUT_HASSUB_SEQUENCE_DATA - * according to sequenceStartPositions - * - * @param sequenceStartPositions[in] input - * subSequenceStartPositions[out] generation output - */ -void generateSubSequenceStartPositions(const IVectorPtr& sequenceStartPositions, - IVectorPtr& subSequenceStartPositions); - -void generateSubSequenceStartPositions( - const ICpuGpuVectorPtr& sequenceStartPositions, - ICpuGpuVectorPtr& subSequenceStartPositions); - -/** - * @brief generate cpuSequenceDims for INPUT_SEQUENCE_MDIM_DATA according to - * sequenceStartPositions - * - * @param sequenceStartPositions[in] input - * cpuSequenceDims[out] generation output - */ -void generateMDimSequenceData(const IVectorPtr& sequenceStartPositions, - IVectorPtr& cpuSequenceDims); -void generateMDimSequenceData(const ICpuGpuVectorPtr& sequenceStartPositions, - IVectorPtr& cpuSequenceDims); - -void checkMatrixEqual(const MatrixPtr& a, const MatrixPtr& b); - -void checkVectorEqual(const IVectorPtr& a, const IVectorPtr& b); -} // namespace paddle diff --git a/paddle/gserver/tests/test_ActivationGrad.cpp b/paddle/gserver/tests/test_ActivationGrad.cpp index 7d7e68da5c..b201ba8a5a 100644 --- a/paddle/gserver/tests/test_ActivationGrad.cpp +++ b/paddle/gserver/tests/test_ActivationGrad.cpp @@ -20,7 +20,7 @@ limitations under the License. */ #include "paddle/trainer/Trainer.h" #include "LayerGradUtil.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT using namespace std; // NOLINT diff --git a/paddle/gserver/tests/test_BatchNorm.cpp b/paddle/gserver/tests/test_BatchNorm.cpp index 7f5fcb670b..822db5a3c4 100644 --- a/paddle/gserver/tests/test_BatchNorm.cpp +++ b/paddle/gserver/tests/test_BatchNorm.cpp @@ -22,7 +22,7 @@ limitations under the License. */ #include "paddle/utils/GlobalConstants.h" #include "LayerGradUtil.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT using namespace std; // NOLINT diff --git a/paddle/gserver/tests/test_ConvTrans.cpp b/paddle/gserver/tests/test_ConvTrans.cpp index dd3378304b..40bb1e2d73 100644 --- a/paddle/gserver/tests/test_ConvTrans.cpp +++ b/paddle/gserver/tests/test_ConvTrans.cpp @@ -23,7 +23,7 @@ limitations under the License. */ #include "paddle/utils/GlobalConstants.h" #include "LayerGradUtil.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT using namespace std; // NOLINT diff --git a/paddle/gserver/tests/test_ConvUnify.cpp b/paddle/gserver/tests/test_ConvUnify.cpp index ad99b50245..207fc0566f 100644 --- a/paddle/gserver/tests/test_ConvUnify.cpp +++ b/paddle/gserver/tests/test_ConvUnify.cpp @@ -23,7 +23,7 @@ limitations under the License. */ #include "paddle/utils/GlobalConstants.h" #include "LayerGradUtil.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT using namespace std; // NOLINT diff --git a/paddle/gserver/tests/test_Evaluator.cpp b/paddle/gserver/tests/test_Evaluator.cpp index e07066dad8..8165eb8269 100644 --- a/paddle/gserver/tests/test_Evaluator.cpp +++ b/paddle/gserver/tests/test_Evaluator.cpp @@ -15,7 +15,7 @@ limitations under the License. */ #include #include #include "ModelConfig.pb.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" #include "paddle/trainer/Trainer.h" using namespace paddle; // NOLINT diff --git a/paddle/gserver/tests/test_LayerGrad.cpp b/paddle/gserver/tests/test_LayerGrad.cpp index 2cc25f6b21..66a70ecd41 100644 --- a/paddle/gserver/tests/test_LayerGrad.cpp +++ b/paddle/gserver/tests/test_LayerGrad.cpp @@ -21,7 +21,7 @@ limitations under the License. */ #include "paddle/trainer/Trainer.h" #include "LayerGradUtil.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT using namespace std; // NOLINT diff --git a/paddle/gserver/tests/test_NetworkCompare.cpp b/paddle/gserver/tests/test_NetworkCompare.cpp index 0d26105955..4db30f37a5 100644 --- a/paddle/gserver/tests/test_NetworkCompare.cpp +++ b/paddle/gserver/tests/test_NetworkCompare.cpp @@ -18,7 +18,7 @@ limitations under the License. */ #include #include -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" #include "paddle/trainer/Trainer.h" #include "paddle/utils/Stat.h" diff --git a/paddle/gserver/tests/test_PriorBox.cpp b/paddle/gserver/tests/test_PriorBox.cpp index a6d6a24269..ae0e3bc3d2 100644 --- a/paddle/gserver/tests/test_PriorBox.cpp +++ b/paddle/gserver/tests/test_PriorBox.cpp @@ -17,7 +17,7 @@ limitations under the License. */ #include #include "LayerGradUtil.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT using namespace std; // NOLINT diff --git a/paddle/gserver/tests/test_ProtoDataProvider.cpp b/paddle/gserver/tests/test_ProtoDataProvider.cpp index 8fc0aaab69..e11bf402c2 100644 --- a/paddle/gserver/tests/test_ProtoDataProvider.cpp +++ b/paddle/gserver/tests/test_ProtoDataProvider.cpp @@ -20,7 +20,7 @@ limitations under the License. */ #include "paddle/gserver/dataproviders/ProtoDataProvider.h" #include "paddle/utils/Util.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace std; // NOLINT diff --git a/paddle/gserver/tests/test_PyDataProvider.cpp b/paddle/gserver/tests/test_PyDataProvider.cpp index 0f264ecf91..db883543c3 100644 --- a/paddle/gserver/tests/test_PyDataProvider.cpp +++ b/paddle/gserver/tests/test_PyDataProvider.cpp @@ -20,7 +20,7 @@ limitations under the License. */ #include "paddle/gserver/dataproviders/PyDataProvider.h" #include "paddle/utils/Util.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace std; // NOLINT using namespace paddle; // NOLINT diff --git a/paddle/gserver/tests/test_RecurrentLayer.cpp b/paddle/gserver/tests/test_RecurrentLayer.cpp index f91c788863..16ab0e6aec 100644 --- a/paddle/gserver/tests/test_RecurrentLayer.cpp +++ b/paddle/gserver/tests/test_RecurrentLayer.cpp @@ -19,7 +19,7 @@ limitations under the License. */ #include "paddle/gserver/layers/DataLayer.h" #include "paddle/gserver/layers/Layer.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT using namespace std; // NOLINT diff --git a/paddle/gserver/tests/test_WarpCTCLayer.cpp b/paddle/gserver/tests/test_WarpCTCLayer.cpp index dab6366588..23ae95852e 100644 --- a/paddle/gserver/tests/test_WarpCTCLayer.cpp +++ b/paddle/gserver/tests/test_WarpCTCLayer.cpp @@ -20,7 +20,7 @@ limitations under the License. */ #include "paddle/gserver/layers/Layer.h" #include "paddle/gserver/layers/WarpCTCLayer.h" -#include "TestUtil.h" +#include "paddle/testing/TestUtil.h" using namespace paddle; // NOLINT using namespace std; // NOLINT diff --git a/paddle/math/tests/CMakeLists.txt b/paddle/math/tests/CMakeLists.txt index a3ea078509..06fc10bae7 100644 --- a/paddle/math/tests/CMakeLists.txt +++ b/paddle/math/tests/CMakeLists.txt @@ -7,8 +7,7 @@ add_simple_unittest(test_SparseMatrix) # TODO(yuyang18): Refactor TestUtil.cpp. Remove this cross module reference. add_unittest(test_matrixCompare - test_matrixCompare.cpp - ../../gserver/tests/TestUtil.cpp) + test_matrixCompare.cpp) add_simple_unittest(test_sparseMatrixCompare) add_simple_unittest(test_perturbation) diff --git a/paddle/math/tests/test_GpuProfiler.cpp b/paddle/math/tests/test_GpuProfiler.cpp index d490078d90..e6b5dba446 100644 --- a/paddle/math/tests/test_GpuProfiler.cpp +++ b/paddle/math/tests/test_GpuProfiler.cpp @@ -15,9 +15,9 @@ limitations under the License. */ #ifndef PADDLE_ONLY_CPU #include -#include "paddle/gserver/tests/TestUtil.h" #include "paddle/math/Matrix.h" #include "paddle/math/SparseMatrix.h" +#include "paddle/testing/TestUtil.h" #include "paddle/utils/Stat.h" #include "paddle/utils/Util.h" diff --git a/paddle/math/tests/test_matrixCompare.cpp b/paddle/math/tests/test_matrixCompare.cpp index 98d63438a5..3a780d26c0 100644 --- a/paddle/math/tests/test_matrixCompare.cpp +++ b/paddle/math/tests/test_matrixCompare.cpp @@ -18,9 +18,9 @@ limitations under the License. */ #include #include "TensorCheck.h" -#include "paddle/gserver/tests/TestUtil.h" #include "paddle/math/Matrix.h" #include "paddle/math/SparseMatrix.h" +#include "paddle/testing/TestUtil.h" #include "paddle/utils/Stat.h" #include "paddle/utils/Util.h" diff --git a/paddle/testing/CMakeLists.txt b/paddle/testing/CMakeLists.txt index 584498c860..c47add04b0 100644 --- a/paddle/testing/CMakeLists.txt +++ b/paddle/testing/CMakeLists.txt @@ -3,4 +3,6 @@ if(WITH_TESTING) add_library(paddle_test_main STATIC TestMain.cpp) add_dependencies(paddle_test_main gen_proto_cpp) + add_library(paddle_test_util STATIC TestUtil.cpp) + add_dependencies(paddle_test_util gen_proto_cpp) endif() From 17953b3a4d8a8f15402968fc56f77e9ad2f71e4d Mon Sep 17 00:00:00 2001 From: xutianbing Date: Tue, 3 Jan 2017 18:12:32 -0800 Subject: [PATCH 087/119] add TestUtil.h and TestUtil.cpp, moving from gserver/tests/ to testing/ --- paddle/testing/TestUtil.cpp | 219 ++++++++++++++++++++++++++++++++++++ paddle/testing/TestUtil.h | 78 +++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 paddle/testing/TestUtil.cpp create mode 100644 paddle/testing/TestUtil.h diff --git a/paddle/testing/TestUtil.cpp b/paddle/testing/TestUtil.cpp new file mode 100644 index 0000000000..c691fe2625 --- /dev/null +++ b/paddle/testing/TestUtil.cpp @@ -0,0 +1,219 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#include "TestUtil.h" +#include +#include "paddle/math/SparseMatrix.h" + +DEFINE_int32(fixed_seq_length, 0, "Produce some sequence of fixed length"); + +namespace paddle { + +std::string randStr(const int len) { + std::string str = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::string s = ""; + for (int i = 0; i < len; ++i) s += str[(rand() % 62)]; // NOLINT + return s; +} + +MatrixPtr makeRandomSparseMatrix(size_t height, + size_t width, + bool withValue, + bool useGpu, + bool equalNnzPerSample) { + std::vector ids(height); + std::vector indices(height + 1); + indices[0] = 0; + + std::function randomer = [] { return uniformRandom(10); }; + if (equalNnzPerSample) { + size_t n = 0; + do { + n = uniformRandom(10); + } while (!n); + randomer = [=] { return n; }; + } + for (size_t i = 0; i < height; ++i) { + indices[i + 1] = indices[i] + std::min(randomer(), width); + ids[i] = i; + } + + if (!withValue) { + std::vector data; + data.resize(indices[height] - indices[0]); + for (size_t i = 0; i < data.size(); ++i) { + data[i].col = uniformRandom(width); + } + auto mat = Matrix::createSparseMatrix( + height, width, data.size(), NO_VALUE, SPARSE_CSR, false, useGpu); + if (useGpu) { + std::dynamic_pointer_cast(mat)->copyFrom( + ids.data(), indices.data(), data.data(), HPPL_STREAM_DEFAULT); + } else { + std::dynamic_pointer_cast(mat)->copyFrom( + ids.data(), indices.data(), data.data()); + } + return mat; + } else { + std::vector data; + data.resize(indices[height] - indices[0]); + for (size_t i = 0; i < data.size(); ++i) { + data[i].col = uniformRandom(width); + data[i].value = rand() / static_cast(RAND_MAX); // NOLINT + } + auto mat = Matrix::createSparseMatrix( + height, width, data.size(), FLOAT_VALUE, SPARSE_CSR, false, useGpu); + if (useGpu) { + std::dynamic_pointer_cast(mat)->copyFrom( + ids.data(), indices.data(), data.data(), HPPL_STREAM_DEFAULT); + } else { + std::dynamic_pointer_cast(mat)->copyFrom( + ids.data(), indices.data(), data.data()); + } + return mat; + } +} + +void generateSequenceStartPositions(size_t batchSize, + IVectorPtr& sequenceStartPositions) { + ICpuGpuVectorPtr gpuCpuVec; + generateSequenceStartPositions(batchSize, gpuCpuVec); + sequenceStartPositions = gpuCpuVec->getMutableVector(false); +} + +void generateSequenceStartPositions(size_t batchSize, + ICpuGpuVectorPtr& sequenceStartPositions) { + int numSeqs; + if (FLAGS_fixed_seq_length != 0) { + numSeqs = std::ceil((float)batchSize / (float)FLAGS_fixed_seq_length); + } else { + numSeqs = batchSize / 10 + 1; + } + sequenceStartPositions = + ICpuGpuVector::create(numSeqs + 1, /* useGpu= */ false); + int* buf = sequenceStartPositions->getMutableData(false); + int64_t pos = 0; + int len = FLAGS_fixed_seq_length; + int maxLen = 2 * batchSize / numSeqs; + for (int i = 0; i < numSeqs; ++i) { + if (FLAGS_fixed_seq_length == 0) { + len = uniformRandom( + std::min(maxLen, batchSize - pos - numSeqs + i)) + + 1; + } + buf[i] = pos; + pos += len; + VLOG(1) << " len=" << len; + } + buf[numSeqs] = batchSize; +} + +void generateSubSequenceStartPositions( + const ICpuGpuVectorPtr& sequenceStartPositions, + ICpuGpuVectorPtr& subSequenceStartPositions) { + int numSeqs = sequenceStartPositions->getSize() - 1; + const int* buf = sequenceStartPositions->getData(false); + int numOnes = 0; + for (int i = 0; i < numSeqs; ++i) { + if (buf[i + 1] - buf[i] == 1) { + ++numOnes; + } + } + // each seq has two sub-seq except length 1 + int numSubSeqs = numSeqs * 2 - numOnes; + subSequenceStartPositions = + ICpuGpuVector::create(numSubSeqs + 1, /* useGpu= */ false); + int* subBuf = subSequenceStartPositions->getMutableData(false); + int j = 0; + for (int i = 0; i < numSeqs; ++i) { + if (buf[i + 1] - buf[i] == 1) { + subBuf[j++] = buf[i]; + } else { + int len = uniformRandom(buf[i + 1] - buf[i] - 1) + 1; + subBuf[j++] = buf[i]; + subBuf[j++] = buf[i] + len; + } + } + subBuf[j] = buf[numSeqs]; +} + +void generateMDimSequenceData(const IVectorPtr& sequenceStartPositions, + IVectorPtr& cpuSequenceDims) { + /* generate sequences with 2 dims */ + int numSeqs = sequenceStartPositions->getSize() - 1; + int numDims = 2; + + cpuSequenceDims = IVector::create(numSeqs * numDims, /* useGpu= */ false); + int* bufStarts = sequenceStartPositions->getData(); + int* bufDims = cpuSequenceDims->getData(); + + for (int i = 0; i < numSeqs; i++) { + int len = bufStarts[i + 1] - bufStarts[i]; + /* get width and height randomly */ + std::vector dimVec; + for (int j = 0; j < len; j++) { + if (len % (j + 1) == 0) { + dimVec.push_back(1); + } + } + int idx = rand() % dimVec.size(); // NOLINT use rand_r + bufDims[i * numDims] = dimVec[idx]; + bufDims[i * numDims + 1] = len / dimVec[idx]; + } +} + +void generateMDimSequenceData(const ICpuGpuVectorPtr& sequenceStartPositions, + IVectorPtr& cpuSequenceDims) { + /* generate sequences with 2 dims */ + int numSeqs = sequenceStartPositions->getSize() - 1; + int numDims = 2; + + cpuSequenceDims = IVector::create(numSeqs * numDims, /* useGpu= */ false); + const int* bufStarts = sequenceStartPositions->getData(false); + int* bufDims = cpuSequenceDims->getData(); + + for (int i = 0; i < numSeqs; i++) { + int len = bufStarts[i + 1] - bufStarts[i]; + /* get width and height randomly */ + std::vector dimVec; + for (int j = 0; j < len; j++) { + if (len % (j + 1) == 0) { + dimVec.push_back(1); + } + } + int idx = rand() % dimVec.size(); // NOLINT use rand_r + bufDims[i * numDims] = dimVec[idx]; + bufDims[i * numDims + 1] = len / dimVec[idx]; + } +} + +void checkMatrixEqual(const MatrixPtr& a, const MatrixPtr& b) { + EXPECT_EQ(a->getWidth(), b->getWidth()); + EXPECT_EQ(a->getHeight(), b->getHeight()); + EXPECT_EQ(a->isTransposed(), b->isTransposed()); + for (size_t r = 0; r < a->getHeight(); ++r) { + for (size_t c = 0; c < a->getWidth(); ++c) { + EXPECT_FLOAT_EQ(a->getElement(r, c), b->getElement(r, c)); + } + } +} + +void checkVectorEqual(const IVectorPtr& a, const IVectorPtr& b) { + EXPECT_EQ(a->getSize(), b->getSize()); + for (size_t r = 0; r < a->getSize(); ++r) { + EXPECT_FLOAT_EQ(a->get(r), b->get(r)); + } +} +} // namespace paddle diff --git a/paddle/testing/TestUtil.h b/paddle/testing/TestUtil.h new file mode 100644 index 0000000000..ec86469aeb --- /dev/null +++ b/paddle/testing/TestUtil.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +#pragma once + +#include +#include "paddle/math/Matrix.h" + +namespace paddle { + +std::string randStr(const int len); + +inline int uniformRandom(int n) { return n == 0 ? 0 : rand() % n; } + +inline bool approximatelyEqual(float a, float b, float epsilon) { + return fabs(a - b) <= ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); +} + +MatrixPtr makeRandomSparseMatrix(size_t height, + size_t width, + bool withValue, + bool useGpu, + bool equalNnzPerSample = false); + +/** + * @brief generate sequenceStartPositions for INPUT_SEQUENCE_DATA, + * INPUT_HASSUB_SEQUENCE_DATA and INPUT_SEQUENCE_LABEL + * + * @param batchSize batchSize + * sequenceStartPositions[out] generation output + */ +void generateSequenceStartPositions(size_t batchSize, + IVectorPtr& sequenceStartPositions); + +void generateSequenceStartPositions(size_t batchSize, + ICpuGpuVectorPtr& sequenceStartPositions); + +/** + * @brief generate subSequenceStartPositions for INPUT_HASSUB_SEQUENCE_DATA + * according to sequenceStartPositions + * + * @param sequenceStartPositions[in] input + * subSequenceStartPositions[out] generation output + */ +void generateSubSequenceStartPositions(const IVectorPtr& sequenceStartPositions, + IVectorPtr& subSequenceStartPositions); + +void generateSubSequenceStartPositions( + const ICpuGpuVectorPtr& sequenceStartPositions, + ICpuGpuVectorPtr& subSequenceStartPositions); + +/** + * @brief generate cpuSequenceDims for INPUT_SEQUENCE_MDIM_DATA according to + * sequenceStartPositions + * + * @param sequenceStartPositions[in] input + * cpuSequenceDims[out] generation output + */ +void generateMDimSequenceData(const IVectorPtr& sequenceStartPositions, + IVectorPtr& cpuSequenceDims); +void generateMDimSequenceData(const ICpuGpuVectorPtr& sequenceStartPositions, + IVectorPtr& cpuSequenceDims); + +void checkMatrixEqual(const MatrixPtr& a, const MatrixPtr& b); + +void checkVectorEqual(const IVectorPtr& a, const IVectorPtr& b); +} // namespace paddle From a080aa7a1c3936fd3e6ad27091873947c178c0dc Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Wed, 4 Jan 2017 10:27:34 +0800 Subject: [PATCH 088/119] Change run_all => entrypoint. --- paddle/scripts/docker/Dockerfile | 4 ++-- paddle/scripts/docker/Dockerfile.gpu | 4 ++-- paddle/scripts/docker/{run_all => entrypoint} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename paddle/scripts/docker/{run_all => entrypoint} (100%) diff --git a/paddle/scripts/docker/Dockerfile b/paddle/scripts/docker/Dockerfile index 46363b05b7..1522be023f 100644 --- a/paddle/scripts/docker/Dockerfile +++ b/paddle/scripts/docker/Dockerfile @@ -50,6 +50,6 @@ WORKDIR "/notes" EXPOSE 8888 RUN mkdir -p /opt/bin -COPY ./paddle/scripts/docker/run_all /opt/bin/ +COPY ./paddle/scripts/docker/entrypoint /opt/bin/ -CMD ["/opt/bin/run_all"] +CMD ["/opt/bin/entrypoint"] diff --git a/paddle/scripts/docker/Dockerfile.gpu b/paddle/scripts/docker/Dockerfile.gpu index 072c144818..09f07043e2 100644 --- a/paddle/scripts/docker/Dockerfile.gpu +++ b/paddle/scripts/docker/Dockerfile.gpu @@ -50,6 +50,6 @@ WORKDIR "/notes" EXPOSE 8888 RUN mkdir -p /opt/bin -COPY ./paddle/scripts/docker/run_all /opt/bin/ +COPY ./paddle/scripts/docker/entrypoint /opt/bin/ -CMD ["/opt/bin/run_all"] +CMD ["/opt/bin/entrypoint"] diff --git a/paddle/scripts/docker/run_all b/paddle/scripts/docker/entrypoint similarity index 100% rename from paddle/scripts/docker/run_all rename to paddle/scripts/docker/entrypoint From e0a85db7f5609da5b881707505144de99613e49c Mon Sep 17 00:00:00 2001 From: Luo Tao Date: Wed, 4 Jan 2017 17:16:28 +0800 Subject: [PATCH 089/119] fix dead links, reduce image size --- doc/howto/deep_model/rnn/rnn_config_cn.rst | 12 +++--------- doc/howto/usage/k8s/k8s_aws_en.md | 6 +++--- doc/tutorials/gan/gan.png | Bin 33275 -> 17810 bytes doc/tutorials/gan/index_en.md | 12 +++--------- doc/tutorials/gan/uniform_sample.png | Bin 20609 -> 24880 bytes 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/doc/howto/deep_model/rnn/rnn_config_cn.rst b/doc/howto/deep_model/rnn/rnn_config_cn.rst index 8d65b3512d..ac2bd0775f 100644 --- a/doc/howto/deep_model/rnn/rnn_config_cn.rst +++ b/doc/howto/deep_model/rnn/rnn_config_cn.rst @@ -33,8 +33,7 @@ PaddlePaddle yield src_ids, trg_ids, trg_ids_next -有关如何编写数据提供程序的更多细节描述,请参考 -`PyDataProvider2 <../../ui/data_provider/index.html>`__\ 。完整的数据提供文件在 +有关如何编写数据提供程序的更多细节描述,请参考 :ref:`api_pydataprovider2` 。完整的数据提供文件在 ``demo/seqToseq/dataprovider.py``\ 。 配置循环神经网络架构 @@ -132,9 +131,7 @@ Sequence to Sequence Model with Attention 模型的编码器部分如下所示。它叫做\ ``grumemory``\ 来表示门控循环神经网络。如果网络架构简单,那么推荐使用循环神经网络的方法,因为它比 ``recurrent_group`` -更快。我们已经实现了大多数常用的循环神经网络架构,可以参考 -`Layers <../../ui/api/trainer_config_helpers/layers_index.html>`__ -了解更多细节。 +更快。我们已经实现了大多数常用的循环神经网络架构,可以参考 :ref:`api_trainer_config_helpers_layers` 了解更多细节。 我们还将编码向量投射到 ``decoder_size`` 维空间。这通过获得反向循环网络的第一个实例,并将其投射到 @@ -276,9 +273,6 @@ attention,门控循环单元单步函数和输出函数: result_file=gen_trans_file) outputs(beam_gen) -注意,这种生成技术只用于类似解码器的生成过程。如果你正在处理序列标记任务,请参阅 -`Semantic Role Labeling -Demo <../../demo/semantic_role_labeling/index.html>`__ -了解更多详细信息。 +注意,这种生成技术只用于类似解码器的生成过程。如果你正在处理序列标记任务,请参阅 :ref:`semantic_role_labeling` 了解更多详细信息。 完整的配置文件在\ ``demo/seqToseq/seqToseq_net.py``\ 。 diff --git a/doc/howto/usage/k8s/k8s_aws_en.md b/doc/howto/usage/k8s/k8s_aws_en.md index 201bcae48d..422dc3bd81 100644 --- a/doc/howto/usage/k8s/k8s_aws_en.md +++ b/doc/howto/usage/k8s/k8s_aws_en.md @@ -331,15 +331,15 @@ For sharing the training data across all the Kubernetes nodes, we use EFS (Elast 1. Make sure you added AmazonElasticFileSystemFullAccess policy in your group. 1. Create the Elastic File System in AWS console, and attach the new VPC with it. - +
![](src/create_efs.png)
1. Modify the Kubernetes security group under ec2/Security Groups, add additional inbound policy "All TCP TCP 0 - 65535 0.0.0.0/0" for Kubernetes default VPC security group. - +
![](src/add_security_group.png)
1. Follow the EC2 mount instruction to mount the disk onto all the Kubernetes nodes, we recommend to mount EFS disk onto ~/efs. - +
![](src/efs_mount.png)
Before starting the training, you should place your user config and divided training data onto EFS. When the training start, each task will copy related files from EFS into container, and it will also write the training results back onto EFS, we will show you how to place the data later in this article. diff --git a/doc/tutorials/gan/gan.png b/doc/tutorials/gan/gan.png index 001ed6cc19e8911f9b10f63211c9658160b3a06e..0eafd7cb49b545f412f8e775804bcd0b22c42454 100644 GIT binary patch literal 17810 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 OAQ@K>FBLWL{a*m;J>miY literal 33275 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

- -

+
![](./gan.png)

Figure 1. GAN-Model-Structure figure credit @@ -111,9 +109,7 @@ $python gan_trainer.py -d uniform --useGpu 1 ``` The generated samples can be found in ./uniform_samples/ and one example is shown below as Figure 2. One can see that it roughly recovers the 2D uniform distribution. -

- -

+
![](./uniform_sample.png)

Figure 2. Uniform Sample

@@ -135,9 +131,7 @@ To train the GAN model on mnist data, one can use the following command: $python gan_trainer.py -d mnist --useGpu 1 ``` The generated sample images can be found at ./mnist_samples/ and one example is shown below as Figure 3. -

- -

+
![](./mnist_sample.png)

Figure 3. MNIST Sample

diff --git a/doc/tutorials/gan/uniform_sample.png b/doc/tutorials/gan/uniform_sample.png index 4a96c45cae82673f5a1df986f2643a8026da7937..e716c48e782019a757bed0cb443f2ed97386cbe2 100644 GIT binary patch literal 24880 zcmdRW1ydZ+7G@v=4DRmk?(Xgq++6~}CAhl=cXyWrcL^jo!QEYh2HEDlw^jQWcB-bR zX1b?u-`jo8`Q%Kbijp)U93I?<4<8U^WhB%-e1Iqh-XdUV;6FDR-~!+e$VE(60}Q-; zz~&LaHiV0swAhE*34){d53NKMML&FKh=+eRfdaN+9c6S}K72rPeSbr&TM~PI_+Z>3 zD*XaiByDXe<88fTDJLiAN=-#3ii89Vh71ZI*|Y?0 zw|e>D8ls?pKoFuLC@QEZp%lnL|NEQ?1tqjN+6IN{KcAt1BuGcu|950QJg{|SiWdS4 z9QA%MbB&?}yv|3M~|BA)G3eqcV+e@{#TmJk(b z606jxKoj(RN_JT9lwGJbASWiypvBP#dTTZc^ua=dLBT!u-sXf_;EDRzB#=Vum>pHC7+Ec|*h35UfXQzRW<8ZY!D^Zy(X8z4TB;2Dmg9$6jJ zI(~C7Nh=UdgC4%j-4f-aGXh~WIhTjZi zs-B)a4HhvkP_K5Z(TXzR>8I7IGVoXo`Ai1wQ^0A8c!Zkm7UJB`S7rCcQi>815*9t@ zUp5}0Mh?uYi+z246^oJ|D1X?@Qax=3BP$dod)B&NtSi30zH<3{{e2?Ibjn@SIUfB+ z?Yc9JNT=UgPn(R^Q(b4+RoPA4Xz@$RQbkTc`5DQm!4zt(-7PwaO3`*@A*Ab+K$XpE zf`B|@C>om{I6D67!uRQ~s^w(v8JNpe+-$yD2Ts$ueQQKD27!=oPPN!o+zMFI_N^h+ zm~^LHBEhf43_6X2swhW66I%n}nA#DpebM9@G-|~WkWhB)vg1}V!2A{0bw5Sb8ucg% zdf#biWtA!A6h1%RRAnX#p5SqPma$)ME~rp1n^|ly`_yEx+ppJ&&1|?>D(kkM=@Hvm z$gbzL;#Ht(VUe*oXk2PK?6rOIZD|({3?`|yr0W#lUdt66Va~)vTX9t{Rj8oL9E?P- zx)XQTHKrV*)M+&5u$`lk%i(KjU+eaN>ptp!o)%PCWR-FX~TYvDzku zh}T^7D=mJe%_XZsDs~tH?Rs^`3H1XALXeaMPFA^zw37!hP`xZJz}(1q%o#D?lJJ=eL|02mmM3eS$bjeuF&7QcY9s^!!(D+&2TAETN4B`W+oWLvb@v zBZCDSC=r>|r{R&V+l(V?zx+F2KF!>N0{Q0(>cicNgGPtDY$mH&kjNexupDh*Yuw=% zaJ~3IHt&h`a`Rp?tC{6JI|acZR$!?j{$A4}(XE7lg$VuR6-D8SFftccn0=d3^MeA{nknpeo zt_W9&M8gySL)=Hw!HxPZ{`v_lcD_1jzD6(prs$K3LR*E)!R}ny?!7Tvo9hM|C-EY zuk-Z!uaAhlqXP;vboU%L;R*_f>RRC6Is8OvrFsEnoaGCLCv{faR`s=>9Q5pk3BUQ8 z$kKozFNXGJ^Y#=FOaI;6u6eDZi85#|{$>xXXCoqW+4t9%`=f{-nsH%BXM1x_F%mv6 zPbqX%8$LHspC~mZd%JtWn?@D2C9N{&m5n%<*%=!zKGbwj5N|vEf71lck$B9olSN&o!B9R29lpbpoIJsSGZgdH(iL zw3#F^9^=NpyR#xlC^!<{OyXE#;q_0AxVG8rUEylsNRc$^RKk@iCRAc6j^?&Clrdj# zO>YL==G3CwvCO6u8BWg&{LB)ds>4D@=!?FtCQ1d&@0u_Q#Xi zEaWQHO5jT5)8ag(In9O<{_%2oDSx@}beH$s{Rxr&h^DsHAKbtrBZ5r4J|LD)e^?@I zACfZ+K3?~YSo^#7hgB)N6^Xdr?}bjqk |6?(2(hcJAWnD<)m>0t*BjdFSGC!8od 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 NMLAX3vZscD{{un^Hgf;~ literal 20609 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 MuJoOZTbi%_7b3Xb=>Px# From c60a321528b3cb103d6f4d549512a830b25cfdfb Mon Sep 17 00:00:00 2001 From: Zhizhong Su Date: Wed, 4 Jan 2017 12:09:31 +0000 Subject: [PATCH 090/119] revise new_layer_cn and fix a few problems in new_layer_en --- doc/howto/dev/new_layer_cn.rst | 61 +++++++++++++++++----------------- doc/howto/dev/new_layer_en.rst | 5 ++- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/doc/howto/dev/new_layer_cn.rst b/doc/howto/dev/new_layer_cn.rst index 8f5df0b36a..d5d37e83d0 100644 --- a/doc/howto/dev/new_layer_cn.rst +++ b/doc/howto/dev/new_layer_cn.rst @@ -2,26 +2,26 @@ 实现新的网络层 ================ -这份教程指导你在PaddlePaddle中实现一个自定义的网络层。在这里我们使用全连接层作为例子来指导你完成实现新网络层需要的几个步骤。 +这份教程展示了如何在PaddlePaddle中实现一个自定义的网络层。在这里我们使用全连接层作为例子来展示实现新网络层所需要的四个步骤。 -- 推导该层前向和后向传递的方程。 -- 实现该层的C++类。 -- 写梯度检测的测试单元,以保证梯度的正确计算。 -- 实现该层的python封装。 +1. 推导该层前向和后向传递的方程。 +2. 实现该层的C++类。 +3. 增加梯度检测的单元测试,以保证梯度的正确计算。 +4. 封装该层的Python接口。 推导方程 ================ 首先我们需要推导该网络层的*前向传播*和*后向传播*的方程。前向传播给定输入,计算输出。后向传播给定输出的梯度,计算输入和参数的梯度。 -下图是一个全链接层的示意图。在全连接层中,每个输出节点都连接到所有的输入节点上。 +下图是一个全连接层的示意图。在全连接层中,每个输出节点都连接到所有的输入节点上。 .. image:: FullyConnected.jpg :align: center :scale: 60 % 一个网络层的前向传播部分把输入转化为相应的输出。 -全连接层以一个维度为 :math:`D_i` 稠密的向量作为输入。其用一个尺度为 :math:`D_i \times D_o` 的变换矩阵 :math:`W` 把 :math:`x` 映射到一个维度为 :math:`D_o` 的向量,并在其上再加上维度为 :math:`D_o` 的偏置向量 :math:`b` 。 +全连接层以一个维度为 :math:`D_i` 的稠密向量作为输入,使用一个尺度为 :math:`D_i \times D_o` 的变换矩阵 :math:`W` 把 :math:`x` 映射到一个维度为 :math:`D_o` 的向量,并在乘积结果上再加上维度为 :math:`D_o` 的偏置向量 :math:`b` 。 .. math:: @@ -29,9 +29,9 @@ 其中 :math:`f(.)` 是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。 -变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传播对所有的参数和输入都计算输出函数的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 +变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传根据输出的梯度,分别计算每个参数的梯度,以及输入的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 -假设我们的损失函数是 :math:`c(y)` ,那么 +假设损失函数是 :math:`c(y)` ,那么 .. math:: @@ -43,9 +43,9 @@ \frac{\partial y}{\partial z} = \frac{\partial f(z)}{\partial z} -我们的base layer类可以自动计算上面的导数。 +PaddlePaddle的base layer类可以自动计算上面的导数。 -因而,对全连接层来说,我们需要计算: +因此,对全连接层来说,我们需要计算: .. math:: @@ -60,23 +60,23 @@ 一个网络层的C++类需要实现初始化,前向和后向。全连接层的实现位于:code:`paddle/gserver/layers/FullyConnectedLayer.h`及:code:`paddle/gserver/layers/FullyConnectedLayer.cpp`。这里我们展示一份简化过的代码。 -这个类需要继承 :code:`paddle::Layer` 这个基类,并且需要重写以下基类中的虚函数: +这个类需要继承 :code:`paddle::Layer` 这个基类,并且需要重写基类中的以下几个虚函数: -- 类的构造函数和析构析构函数。 +- 类的构造函数和析构函数。 - :code:`init` 函数。用于初始化参数和设置。 - :code:`forward` 。实现网络层的前向传播。 - :code:`backward` 。实现网络层的后向传播。 -- :code:`prefetch` 。用于确定由参数服务器预取的行相关的参数矩阵。如果该网络层不需要远程稀疏更新的话,你不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) +- :code:`prefetch` 。用来从参数服务器预取参数矩阵相应的行。如果网络层不需要远程稀疏更新,则不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) -头文件在下面列出: +头文件如下: .. code-block:: c++ namespace paddle { /** * 全连接层的每个输出都连接到上一层的所有的神经元上。 - * 其用一些学习过的参数做内积并加上偏置(可选)。 + * 它的输入与经过学习的参数做内积并加上偏置(可选)。 * * 配置文件接口是fc_layer。 */ @@ -101,9 +101,9 @@ }; } // namespace paddle -头文件中把参数定位为类的成员变量。我们使用 :code:`Weight` 类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中由详细介绍。 +头文件中把参数定义为类的成员变量。我们使用 :code:`Weight` 类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中详细介绍。 -- :code:`weights_` 是存有变换矩阵的一系列权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 +- :code:`weights_` 是存有一系列变换矩阵的权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 - :code:`biases_` 是存有偏置向量的权重。 全连接层没有网络层配置的超参数。如果一个网络层需要配置的话,通常的做法是将配置存于 :code:`LayerConfig& config` 中,并在类构建函数中把它放入一个类成员变量里。 @@ -173,7 +173,7 @@ MatrixPtr outV = getOutputValue(); - // 对每个输入乘上转化矩阵 + // 对每个输入乘上变换矩阵 for (size_t i = 0; i != inputLayers_.size(); ++i) { auto input = getInput(i); CHECK(input.value) << "The input of 'fc' layer must be matrix"; @@ -193,9 +193,9 @@ 实现后向传播的部分有下面几个步骤。 -- :code:`backwardActivation()` 计算激活函数的梯度。梯度会就地(不使用额外空间)乘上输出的梯度,并可以通过 :code:`getOutputGrad()` 来获得。 -- 计算偏置的梯度。注意,我们使用 :code:`biases_->getWGrad()` 来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用 :code:`getParameterPtr()->incUpdate(callback);` 。这是用来在多线程和多机上更新参数的。 -- 之后,计算转换矩阵和输入的梯度,并对相应的参数调用 :code:`incUpdate` 。这给了框架一个机会去了解自己是否已经把所有的梯度收集到一个参数中,使得框架可以进行有时间重叠的工作。(例如,网络通信) +- :code:`backwardActivation()` 计算激活函数的梯度。通过 :code:`getOutputGrad()` 来获得输出的梯度,调用该函数后,梯度会就地(不使用额外空间)乘上输出的梯度。 +- 计算偏置的梯度。注意,我们使用 :code:`biases_->getWGrad()` 来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用 :code:`getParameterPtr()->incUpdate(callback);` 。这用于在多线程和多机上更新参数。 +- 最后,计算转换矩阵和输入的梯度,并对相应的参数调用 :code:`incUpdate` 。PaddlePaddle可以通过该机制判断是否已经收集齐所有的梯度,从而可以做一些与计算重叠的工作(例如,网络通信)。 .. code-block:: c++ @@ -208,7 +208,6 @@ if (biases_ && biases_->getWGrad()) { biases_->getWGrad()->collectBias(*getOutputGrad(), 1); - /* 加上偏置的梯度 */ biases_->getParameterPtr()->incUpdate(callback); } @@ -238,7 +237,7 @@ } } - :code:`prefetch` 函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。在远程稀疏训练时,完整的参数矩阵被分布式的保存在参数服务器上。当网络层用一个批次做训练时,该批次中,输入仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的转换矩阵的那些行。 :code:`prefetch` 表明了这些行的标号。 + :code:`prefetch` 函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。使用远程稀疏方式训练时,完整的参数矩阵被分布在不同的参数服务器上。当网络层用一个批次做训练时,该批次的输入中仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的变换矩阵的那些行。 :code:`prefetch` 表明了这些行的标号。 大多数层不需要远程稀疏训练函数。这种情况下不需要重写该函数。 @@ -271,7 +270,7 @@ 写梯度检查单元测试是一个验证新实现的层是否正确的相对简单的办法。梯度检查单元测试通过有限差分法来验证一个层的梯度。首先对输入做一个小的扰动 :math:`\Delta x` ,然后观察到输出的变化为 :math:`\Delta y` ,那么,梯度就可以通过这个方程计算得到 :math:`\frac{\Delta y}{\Delta x }` 。之后,再用这个梯度去和 :code:`backward` 函数得到的梯度去对比,以保证梯度计算的正确性。需要注意的是梯度检查仅仅验证了梯度的计算,并不保证 :code:`forward` 和 :code:`backward` 函数的实现是正确的。你需要一些更复杂的单元测试来保证你实现的网络层是正确的。 -所有的梯度检测单侧都位于 :code:`paddle/gserver/tests/test_LayerGrad.cpp` 。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: +所有网络层的梯度检查单测都位于 :code:`paddle/gserver/tests/test_LayerGrad.cpp` 。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: + 生成网络层配置。网络层配置包含以下几项: - 偏置参数的大小。(例子中是4096) @@ -294,10 +293,10 @@ - 非零数字的个数,仅对稀疏数据有效。 - 稀疏数据的格式,仅对稀疏数据有效。 + 对每个输入,都需要调用一次 :code:`config.layerConfig.add_inputs();` 。 -+ 调用 :code:`testLayerGrad` 来做梯度检查。它包含下面的参数。 ++ 调用 :code:`testLayerGrad` 来做梯度检查。它包含以下参数。 - 层和输入的配置。(例子中是 :code:`config` ) - - 输入的类型。(例子中是 :code:`fc` ) - - 梯度检查的批次大小。(例子中是100) + - 网络层的类型。(例子中是 :code:`fc` ) + - 梯度检查的输入数据的批次大小。(例子中是100) - 输入是否是转置的。大多数层需要设置为 :code:`false` 。(例子中是 :code:`false` ) - 是否使用权重。有些层或者激活需要做归一化以保证它们的输出的和是一个常数。例如,softmax激活的输出的和总是1。在这种情况下,我们不能通过常规的梯度检查的方式来计算梯度。因此我们采用输出的加权和(非常数)来计算梯度。(例子中是 :code:`true` ,因为全连接层的激活可以是softmax) @@ -309,7 +308,7 @@ config.biasSize = 4096; config.layerConfig.set_type("fc"); config.layerConfig.set_size(4096); - config.layerConfig.set_active_type("sigmoid"); + config.layerConfig.set_active_type("softmax"); config.layerConfig.set_drop_rate(0.1); // Setup inputs. config.inputDefs.push_back( @@ -323,7 +322,7 @@ } } -如果你要为了测试而增加新的文件,例如 :code:`paddle/gserver/tests/testFCGrad.cpp` ,你需要把该文件加入 :code:`paddle/gserver/tests/CMakeLists.txt` 中。下面给出了一个例子。当你执行命令 :code:`make tests` 时,所有的单侧都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单侧正确执行。你需要在配置cmake时将 :code:`WITH_DOUBLE` 设置为 `ON` 。 +如果你要为了测试而增加新的文件,例如 :code:`paddle/gserver/tests/testFCGrad.cpp` ,你需要把该文件加入 :code:`paddle/gserver/tests/CMakeLists.txt` 中。下面给出了一个例子。当你执行命令 :code:`make tests` 时,所有的单测都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单测正确执行。你需要在配置cmake时将 :code:`WITH_DOUBLE` 设置为 `ON` 。 .. code-block:: bash @@ -344,7 +343,7 @@ python封装的实现使得我们可以在配置文件中使用新实现的网 - 所有的Python封装都使用 :code:`@config_layer('fc')` 这样的装饰器。网络层的标识符为 :code:`fc` 。 - 实现构造函数 :code:`__init__` 。 - 它首先调用基构造函数 :code:`super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs)` 。 :code:`FCLayer` 是Python封装的类名。 :code:`fc` 是网络层的标识符。为了封装能够正确工作,这些名字必须要写对。 - - 之后,计算转换矩阵的大小和格式(是否稀疏)。 + - 之后,计算变换矩阵的大小和格式(是否稀疏)。 .. code-block:: python diff --git a/doc/howto/dev/new_layer_en.rst b/doc/howto/dev/new_layer_en.rst index 0513f068f3..46481f5ead 100644 --- a/doc/howto/dev/new_layer_en.rst +++ b/doc/howto/dev/new_layer_en.rst @@ -209,7 +209,6 @@ The implementation of the backward part has the following steps. if (biases_ && biases_->getWGrad()) { biases_->getWGrad()->collectBias(*getOutputGrad(), 1); - /* Increasing the number of gradient */ biases_->getParameterPtr()->incUpdate(callback); } @@ -297,7 +296,7 @@ All the gradient check unit tests are located in :code:`paddle/gserver/tests/tes + each inputs needs to call :code:`config.layerConfig.add_inputs();` once. + call :code:`testLayerGrad` to perform gradient checks. It has the following arguments. - layer and input configurations. (:code:`config` in our example) - - type of the input. (:code:`fc` in our example) + - type of the layer. (:code:`fc` in our example) - batch size of the gradient check. (100 in our example) - whether the input is transpose. Most layers need to set it to :code:`false`. (:code:`false` in our example) - whether to use weights. Some layers or activations perform normalization so that the sum of their output is a constant. For example, the sum of output of a softmax activation is one. In this case, we cannot correctly compute the gradients using regular gradient check techniques. A weighted sum of the output, which is not a constant, is utilized to compute the gradients. (:code:`true` in our example, because the activation of a fully connected layer can be softmax) @@ -310,7 +309,7 @@ All the gradient check unit tests are located in :code:`paddle/gserver/tests/tes config.biasSize = 4096; config.layerConfig.set_type("fc"); config.layerConfig.set_size(4096); - config.layerConfig.set_active_type("sigmoid"); + config.layerConfig.set_active_type("softmax"); config.layerConfig.set_drop_rate(0.1); // Setup inputs. config.inputDefs.push_back( From 0e7d77f325a38531a9a84e6cdcde81355ffcc754 Mon Sep 17 00:00:00 2001 From: Zhizhong Su Date: Wed, 4 Jan 2017 12:52:15 +0000 Subject: [PATCH 091/119] change format --- doc/howto/dev/new_layer_cn.rst | 778 ++++++++++++++++----------------- 1 file changed, 389 insertions(+), 389 deletions(-) diff --git a/doc/howto/dev/new_layer_cn.rst b/doc/howto/dev/new_layer_cn.rst index d5d37e83d0..897a8be5b3 100644 --- a/doc/howto/dev/new_layer_cn.rst +++ b/doc/howto/dev/new_layer_cn.rst @@ -1,389 +1,389 @@ -================ -实现新的网络层 -================ - -这份教程展示了如何在PaddlePaddle中实现一个自定义的网络层。在这里我们使用全连接层作为例子来展示实现新网络层所需要的四个步骤。 - -1. 推导该层前向和后向传递的方程。 -2. 实现该层的C++类。 -3. 增加梯度检测的单元测试,以保证梯度的正确计算。 -4. 封装该层的Python接口。 - -推导方程 -================ - -首先我们需要推导该网络层的*前向传播*和*后向传播*的方程。前向传播给定输入,计算输出。后向传播给定输出的梯度,计算输入和参数的梯度。 - -下图是一个全连接层的示意图。在全连接层中,每个输出节点都连接到所有的输入节点上。 - -.. image:: FullyConnected.jpg - :align: center - :scale: 60 % - -一个网络层的前向传播部分把输入转化为相应的输出。 -全连接层以一个维度为 :math:`D_i` 的稠密向量作为输入,使用一个尺度为 :math:`D_i \times D_o` 的变换矩阵 :math:`W` 把 :math:`x` 映射到一个维度为 :math:`D_o` 的向量,并在乘积结果上再加上维度为 :math:`D_o` 的偏置向量 :math:`b` 。 - -.. math:: - - y = f(W^T x + b) - -其中 :math:`f(.)` 是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。 - -变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传根据输出的梯度,分别计算每个参数的梯度,以及输入的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 - -假设损失函数是 :math:`c(y)` ,那么 - -.. math:: - - \frac{\partial c(y)}{\partial x} = \frac{\partial c(y)}{\partial y} \frac{\partial y}{\partial x} - -假设 :math:`z = f(W^T x + b)` ,那么 - -.. math:: - - \frac{\partial y}{\partial z} = \frac{\partial f(z)}{\partial z} - -PaddlePaddle的base layer类可以自动计算上面的导数。 - -因此,对全连接层来说,我们需要计算: - -.. math:: - - \frac{\partial z}{\partial x} = W, \frac{\partial z_j}{\partial W_{ij}} = x_i, \frac{\partial z}{\partial b} = \mathbf 1 - -其中 :math:`\mathbf 1` 是一个全1的向量, :math:`W_{ij}` 是矩阵 :math:`W` 第i行第j列的数值, :math:`z_j` 是向量 :math:`z` 的第j个值, :math:`x_i` 是向量 :math:`x` 的第i个值。 - -最后我们使用链式法则计算 :math:`\frac{\partial z}{\partial x}` 以及 :math:`\frac{\partial z}{\partial W}` 。计算的细节将在下面的小节给出。 - -实现C++类 -=================== - -一个网络层的C++类需要实现初始化,前向和后向。全连接层的实现位于:code:`paddle/gserver/layers/FullyConnectedLayer.h`及:code:`paddle/gserver/layers/FullyConnectedLayer.cpp`。这里我们展示一份简化过的代码。 - -这个类需要继承 :code:`paddle::Layer` 这个基类,并且需要重写基类中的以下几个虚函数: - -- 类的构造函数和析构函数。 -- :code:`init` 函数。用于初始化参数和设置。 -- :code:`forward` 。实现网络层的前向传播。 -- :code:`backward` 。实现网络层的后向传播。 -- :code:`prefetch` 。用来从参数服务器预取参数矩阵相应的行。如果网络层不需要远程稀疏更新,则不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) - - -头文件如下: - -.. code-block:: c++ - - namespace paddle { - /** - * 全连接层的每个输出都连接到上一层的所有的神经元上。 - * 它的输入与经过学习的参数做内积并加上偏置(可选)。 - * - * 配置文件接口是fc_layer。 - */ - - class FullyConnectedLayer : public Layer { - protected: - WeightList weights_; - std::unique_ptr biases_; - - public: - explicit FullyConnectedLayer(const LayerConfig& config) - : Layer(config) {} - ~FullyConnectedLayer() {} - - bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); - - Weight& getWeight(int idx) { return *weights_[idx]; } - - void prefetch(); - void forward(PassType passType); - void backward(const UpdateCallback& callback = nullptr); - }; - } // namespace paddle - -头文件中把参数定义为类的成员变量。我们使用 :code:`Weight` 类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中详细介绍。 - -- :code:`weights_` 是存有一系列变换矩阵的权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 -- :code:`biases_` 是存有偏置向量的权重。 - -全连接层没有网络层配置的超参数。如果一个网络层需要配置的话,通常的做法是将配置存于 :code:`LayerConfig& config` 中,并在类构建函数中把它放入一个类成员变量里。 - -下面的代码片段实现了 :code:`init` 函数。 - -- 首先,所有的 :code:`init` 函数必须先调用基类中的函数 :code:`Layer::init(layerMap, parameterMap);` 。该语句会为每个层初始化其所需要的变量和连接。 -- 之后初始化所有的权重矩阵 :math:`W` 。当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。 -- 最后,初始化偏置向量。 - - -.. code-block:: c++ - - bool FullyConnectedLayer::init(const LayerMap& layerMap, - const ParameterMap& parameterMap) { - /* 初始化父类 */ - Layer::init(layerMap, parameterMap); - - /* 初始化权重表 */ - CHECK(inputLayers_.size() == parameters_.size()); - for (size_t i = 0; i < inputLayers_.size(); i++) { - // 获得参数尺寸 - size_t height = inputLayers_[i]->getSize(); - size_t width = getSize(); - - // 新建一个权重 - if (parameters_[i]->isSparse()) { - CHECK_LE(parameters_[i]->getSize(), width * height); - } else { - CHECK_EQ(parameters_[i]->getSize(), width * height); - } - Weight* w = new Weight(height, width, parameters_[i]); - - // 将新建的权重加入权重表 - weights_.emplace_back(w); - } - - /* 初始化biases_ */ - if (biasParameter_.get() != NULL) { - biases_ = std::unique_ptr(new Weight(1, getSize(), biasParameter_)); - } - - return true; - } - -实现前向传播的部分有下面几个步骤。 - -- 每个层在其 :code:`forward` 函数的开头必须调用 :code:`Layer::forward(passType);` 。 -- 之后使用 :code:`reserveOutput(batchSize, size);` 为输出分配内存。由于我们支持训练数据有不同的批次大小,所以这一步是必要的。 :code:`reserveOutput` 会相应地改变输出的尺寸。为了保证效率,如果需要扩大矩阵,我们会重新分配内存;如果需要缩减矩阵,我们会继续使用现有的内存块。 -- 之后使用矩阵运算函数来计算 :math:`\sum_i W_i x + b`。:code:`getInput(i).value` 返回第i个输入矩阵。每个输入都是一个 :math:`batchSize \times dim` 的矩阵,每行表示一个批次中的单个输入。对于我们支持的全部矩阵操作,请参考 :code:`paddle/math/Matrix.h`和:code:`paddle/math/BaseMatrix.h` 。 -- 最终,使用 :code:`forwardActivation();` 进行激活操作。这会自动进行网络配置中声明的激活操作。 - - -.. code-block:: c++ - - void FullyConnectedLayer::forward(PassType passType) { - Layer::forward(passType); - - /* 若有必要,为output_申请内存 */ - int batchSize = getInput(0).getBatchSize(); - int size = getSize(); - - { - // 设置输出的尺寸 - reserveOutput(batchSize, size); - } - - MatrixPtr outV = getOutputValue(); - - // 对每个输入乘上变换矩阵 - for (size_t i = 0; i != inputLayers_.size(); ++i) { - auto input = getInput(i); - CHECK(input.value) << "The input of 'fc' layer must be matrix"; - i == 0 ? outV->mul(input.value, weights_[i]->getW(), 1, 0) - : outV->mul(input.value, weights_[i]->getW(), 1, 1); - } - - /* 加上偏置向量 */ - if (biases_.get() != NULL) { - outV->addBias(*(biases_->getW()), 1); - } - - /* 激活 */ { - forwardActivation(); - } - } - -实现后向传播的部分有下面几个步骤。 - -- :code:`backwardActivation()` 计算激活函数的梯度。通过 :code:`getOutputGrad()` 来获得输出的梯度,调用该函数后,梯度会就地(不使用额外空间)乘上输出的梯度。 -- 计算偏置的梯度。注意,我们使用 :code:`biases_->getWGrad()` 来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用 :code:`getParameterPtr()->incUpdate(callback);` 。这用于在多线程和多机上更新参数。 -- 最后,计算转换矩阵和输入的梯度,并对相应的参数调用 :code:`incUpdate` 。PaddlePaddle可以通过该机制判断是否已经收集齐所有的梯度,从而可以做一些与计算重叠的工作(例如,网络通信)。 - - -.. code-block:: c++ - - void FullyConnectedLayer::backward(const UpdateCallback& callback) { - /* 对激活求导 */ { - backwardActivation(); - } - - if (biases_ && biases_->getWGrad()) { - biases_->getWGrad()->collectBias(*getOutputGrad(), 1); - - biases_->getParameterPtr()->incUpdate(callback); - } - - bool syncFlag = hl_get_sync_flag(); - - for (size_t i = 0; i != inputLayers_.size(); ++i) { - /* 计算当前层权重的梯度 */ - if (weights_[i]->getWGrad()) { - MatrixPtr input_T = getInputValue(i)->getTranspose(); - MatrixPtr oGrad = getOutputGrad(); - { - weights_[i]->getWGrad()->mul(input_T, oGrad, 1, 1); - } - } - - - /* 计算输入层的偏差 */ - MatrixPtr preGrad = getInputGrad(i); - if (NULL != preGrad) { - MatrixPtr weights_T = weights_[i]->getW()->getTranspose(); - preGrad->mul(getOutputGrad(), weights_T, 1, 1); - } - - { - weights_[i]->getParameterPtr()->incUpdate(callback); - } - } - } - - :code:`prefetch` 函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。使用远程稀疏方式训练时,完整的参数矩阵被分布在不同的参数服务器上。当网络层用一个批次做训练时,该批次的输入中仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的变换矩阵的那些行。 :code:`prefetch` 表明了这些行的标号。 - -大多数层不需要远程稀疏训练函数。这种情况下不需要重写该函数。 - -.. code-block:: c++ - - void FullyConnectedLayer::prefetch() { - for (size_t i = 0; i != inputLayers_.size(); ++i) { - auto* sparseParam = - dynamic_cast(weights_[i]->getW().get()); - if (sparseParam) { - MatrixPtr input = getInputValue(i); - sparseParam->addRows(input); - } - } - } - -最后,使用 :code:`REGISTER_LAYER(fc, FullyConnectedLayer);` 来注册该层。 :code:`fc` 是该层的标识符, :code:`FullyConnectedLayer` 是该层的类名。 - -.. code-block:: c++ - - namespace paddle { - REGISTER_LAYER(fc, FullyConnectedLayer); - } - -若 :code:`cpp` 被放在 :code:`paddle/gserver/layers` 目录下,其会自动被加入编译列表。 - - -写梯度检查单元测试 -=============================== - -写梯度检查单元测试是一个验证新实现的层是否正确的相对简单的办法。梯度检查单元测试通过有限差分法来验证一个层的梯度。首先对输入做一个小的扰动 :math:`\Delta x` ,然后观察到输出的变化为 :math:`\Delta y` ,那么,梯度就可以通过这个方程计算得到 :math:`\frac{\Delta y}{\Delta x }` 。之后,再用这个梯度去和 :code:`backward` 函数得到的梯度去对比,以保证梯度计算的正确性。需要注意的是梯度检查仅仅验证了梯度的计算,并不保证 :code:`forward` 和 :code:`backward` 函数的实现是正确的。你需要一些更复杂的单元测试来保证你实现的网络层是正确的。 - -所有网络层的梯度检查单测都位于 :code:`paddle/gserver/tests/test_LayerGrad.cpp` 。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: - -+ 生成网络层配置。网络层配置包含以下几项: - - 偏置参数的大小。(例子中是4096) - - 层的类型。(例子中是fc) - - 层的大小。(例子中是4096) - - 激活的类型。(例子中是softmax) - - dropout的比例。(例子中是0.1) -+ 配置网络层的输入。在这个例子里,我们仅有一个输入。 - - 输入的类型( :code:`INPUT_DATA` ),可以是以下几种: - - :code:`INPUT_DATA` :稠密向量。 - - :code:`INPUT_LABEL` :整数。 - - :code:`INPUT_DATA_TARGET` :稠密向量,但不用于计算梯度。 - - :code:`INPUT_SEQUENCE_DATA` :含有序列信息的稠密向量。 - - :code:`INPUT_HASSUB_SEQUENCE_DATA` :含有序列信息和子序列信息的稠密向量。 - - :code:`INPUT_SEQUENCE_LABEL` :含有序列信息的整数。 - - :code:`INPUT_SPARSE_NON_VALUE_DATA` :0-1稀疏数据。 - - :code:`INPUT_SPARSE_FLOAT_VALUE_DATA` :浮点稀疏数据。 - - 输入的名字。(例子中是 :code:`layer_0` ) - - 输入的大小。(例子中是8192) - - 非零数字的个数,仅对稀疏数据有效。 - - 稀疏数据的格式,仅对稀疏数据有效。 -+ 对每个输入,都需要调用一次 :code:`config.layerConfig.add_inputs();` 。 -+ 调用 :code:`testLayerGrad` 来做梯度检查。它包含以下参数。 - - 层和输入的配置。(例子中是 :code:`config` ) - - 网络层的类型。(例子中是 :code:`fc` ) - - 梯度检查的输入数据的批次大小。(例子中是100) - - 输入是否是转置的。大多数层需要设置为 :code:`false` 。(例子中是 :code:`false` ) - - 是否使用权重。有些层或者激活需要做归一化以保证它们的输出的和是一个常数。例如,softmax激活的输出的和总是1。在这种情况下,我们不能通过常规的梯度检查的方式来计算梯度。因此我们采用输出的加权和(非常数)来计算梯度。(例子中是 :code:`true` ,因为全连接层的激活可以是softmax) - -.. code-block:: c++ - - void testFcLayer(string format, size_t nnz) { - // Create layer configuration. - TestConfig config; - config.biasSize = 4096; - config.layerConfig.set_type("fc"); - config.layerConfig.set_size(4096); - config.layerConfig.set_active_type("softmax"); - config.layerConfig.set_drop_rate(0.1); - // Setup inputs. - config.inputDefs.push_back( - {INPUT_DATA, "layer_0", 8192, nnz, ParaSparse(format)}); - config.layerConfig.add_inputs(); - LOG(INFO) << config.inputDefs[0].sparse.sparse << " " - << config.inputDefs[0].sparse.format; - for (auto useGpu : {false, true}) { - testLayerGrad(config, "fc", 100, /* trans */ false, useGpu, - /* weight */ true); - } - } - -如果你要为了测试而增加新的文件,例如 :code:`paddle/gserver/tests/testFCGrad.cpp` ,你需要把该文件加入 :code:`paddle/gserver/tests/CMakeLists.txt` 中。下面给出了一个例子。当你执行命令 :code:`make tests` 时,所有的单测都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单测正确执行。你需要在配置cmake时将 :code:`WITH_DOUBLE` 设置为 `ON` 。 - -.. code-block:: bash - - add_unittest_without_exec(test_FCGrad - test_FCGrad.cpp - LayerGradUtil.cpp - TestUtil.cpp) - - add_test(NAME test_FCGrad - COMMAND test_FCGrad) - - -实现python封装 -======================== - -python封装的实现使得我们可以在配置文件中使用新实现的网络层。所有的python封装都在 :code:`python/paddle/trainer/config_parser.py` 中。全连接层python封装的例子中包含下面几步: - -- 所有的Python封装都使用 :code:`@config_layer('fc')` 这样的装饰器。网络层的标识符为 :code:`fc` 。 -- 实现构造函数 :code:`__init__` 。 - - 它首先调用基构造函数 :code:`super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs)` 。 :code:`FCLayer` 是Python封装的类名。 :code:`fc` 是网络层的标识符。为了封装能够正确工作,这些名字必须要写对。 - - 之后,计算变换矩阵的大小和格式(是否稀疏)。 - -.. code-block:: python - - @config_layer('fc') - class FCLayer(LayerBase): - def __init__( - self, - name, - size, - inputs, - bias=True, - **xargs): - super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs) - for input_index in xrange(len(self.inputs)): - input_layer = self.get_input_layer(input_index) - psize = self.config.size * input_layer.size - dims = [input_layer.size, self.config.size] - format = self.inputs[input_index].format - sparse = format == "csr" or format == "csc" - if sparse: - psize = self.inputs[input_index].nnz - self.create_input_parameter(input_index, psize, dims, sparse, format) - self.create_bias_parameter(bias, self.config.size) - -在网络配置中,网络层的细节可以通过下面这些代码片段来指定。这个类的参数包括: - -- :code:`name` 是网络层实例的名字标识符。 -- :code:`type` 是网络层的类型,通过网络层的标识符来指定。 -- :code:`size` 是网络层输出的大小。 -- :code:`bias` 表明这个层的一个实例是否需要偏置。 -- :code:`inputs` 说明这个层的输入,输入是由一个list中的网络层实例的名字组成的。 - -.. code-block:: python - - Layer( - name = "fc1", - type = "fc", - size = 64, - bias = True, - inputs = [Input("pool3")] - ) - -我们建议你为你的Python封装实现一个“助手”,使得搭模型时更方便。具体可以参考 :code:`python/paddle/trainer_config_helpers/layers.py` 。 +================ +实现新的网络层 +================ + +这份教程展示了如何在PaddlePaddle中实现一个自定义的网络层。在这里我们使用全连接层作为例子来展示实现新网络层所需要的四个步骤。 + +1. 推导该层前向和后向传递的方程。 +2. 实现该层的C++类。 +3. 增加梯度检测的单元测试,以保证梯度的正确计算。 +4. 封装该层的Python接口。 + +推导方程 +================ + +首先我们需要推导该网络层的*前向传播*和*后向传播*的方程。前向传播给定输入,计算输出。后向传播给定输出的梯度,计算输入和参数的梯度。 + +下图是一个全连接层的示意图。在全连接层中,每个输出节点都连接到所有的输入节点上。 + +.. image:: FullyConnected.jpg + :align: center + :scale: 60 % + +一个网络层的前向传播部分把输入转化为相应的输出。 +全连接层以一个维度为 :math:`D_i` 的稠密向量作为输入,使用一个尺度为 :math:`D_i \times D_o` 的变换矩阵 :math:`W` 把 :math:`x` 映射到一个维度为 :math:`D_o` 的向量,并在乘积结果上再加上维度为 :math:`D_o` 的偏置向量 :math:`b` 。 + +.. math:: + + y = f(W^T x + b) + +其中 :math:`f(.)` 是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。 + +变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传根据输出的梯度,分别计算每个参数的梯度,以及输入的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 + +假设损失函数是 :math:`c(y)` ,那么 + +.. math:: + + \frac{\partial c(y)}{\partial x} = \frac{\partial c(y)}{\partial y} \frac{\partial y}{\partial x} + +假设 :math:`z = f(W^T x + b)` ,那么 + +.. math:: + + \frac{\partial y}{\partial z} = \frac{\partial f(z)}{\partial z} + +PaddlePaddle的base layer类可以自动计算上面的导数。 + +因此,对全连接层来说,我们需要计算: + +.. math:: + + \frac{\partial z}{\partial x} = W, \frac{\partial z_j}{\partial W_{ij}} = x_i, \frac{\partial z}{\partial b} = \mathbf 1 + +其中 :math:`\mathbf 1` 是一个全1的向量, :math:`W_{ij}` 是矩阵 :math:`W` 第i行第j列的数值, :math:`z_j` 是向量 :math:`z` 的第j个值, :math:`x_i` 是向量 :math:`x` 的第i个值。 + +最后我们使用链式法则计算 :math:`\frac{\partial z}{\partial x}` 以及 :math:`\frac{\partial z}{\partial W}` 。计算的细节将在下面的小节给出。 + +实现C++类 +=================== + +一个网络层的C++类需要实现初始化,前向和后向。全连接层的实现位于:code:`paddle/gserver/layers/FullyConnectedLayer.h`及:code:`paddle/gserver/layers/FullyConnectedLayer.cpp`。这里我们展示一份简化过的代码。 + +这个类需要继承 :code:`paddle::Layer` 这个基类,并且需要重写基类中的以下几个虚函数: + +- 类的构造函数和析构函数。 +- :code:`init` 函数。用于初始化参数和设置。 +- :code:`forward` 。实现网络层的前向传播。 +- :code:`backward` 。实现网络层的后向传播。 +- :code:`prefetch` 。用来从参数服务器预取参数矩阵相应的行。如果网络层不需要远程稀疏更新,则不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) + + +头文件如下: + +.. code-block:: c++ + + namespace paddle { + /** + * 全连接层的每个输出都连接到上一层的所有的神经元上。 + * 它的输入与经过学习的参数做内积并加上偏置(可选)。 + * + * 配置文件接口是fc_layer。 + */ + + class FullyConnectedLayer : public Layer { + protected: + WeightList weights_; + std::unique_ptr biases_; + + public: + explicit FullyConnectedLayer(const LayerConfig& config) + : Layer(config) {} + ~FullyConnectedLayer() {} + + bool init(const LayerMap& layerMap, const ParameterMap& parameterMap); + + Weight& getWeight(int idx) { return *weights_[idx]; } + + void prefetch(); + void forward(PassType passType); + void backward(const UpdateCallback& callback = nullptr); + }; + } // namespace paddle + +头文件中把参数定义为类的成员变量。我们使用 :code:`Weight` 类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中详细介绍。 + +- :code:`weights_` 是存有一系列变换矩阵的权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 +- :code:`biases_` 是存有偏置向量的权重。 + +全连接层没有网络层配置的超参数。如果一个网络层需要配置的话,通常的做法是将配置存于 :code:`LayerConfig& config` 中,并在类构建函数中把它放入一个类成员变量里。 + +下面的代码片段实现了 :code:`init` 函数。 + +- 首先,所有的 :code:`init` 函数必须先调用基类中的函数 :code:`Layer::init(layerMap, parameterMap);` 。该语句会为每个层初始化其所需要的变量和连接。 +- 之后初始化所有的权重矩阵 :math:`W` 。当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。 +- 最后,初始化偏置向量。 + + +.. code-block:: c++ + + bool FullyConnectedLayer::init(const LayerMap& layerMap, + const ParameterMap& parameterMap) { + /* 初始化父类 */ + Layer::init(layerMap, parameterMap); + + /* 初始化权重表 */ + CHECK(inputLayers_.size() == parameters_.size()); + for (size_t i = 0; i < inputLayers_.size(); i++) { + // 获得参数尺寸 + size_t height = inputLayers_[i]->getSize(); + size_t width = getSize(); + + // 新建一个权重 + if (parameters_[i]->isSparse()) { + CHECK_LE(parameters_[i]->getSize(), width * height); + } else { + CHECK_EQ(parameters_[i]->getSize(), width * height); + } + Weight* w = new Weight(height, width, parameters_[i]); + + // 将新建的权重加入权重表 + weights_.emplace_back(w); + } + + /* 初始化biases_ */ + if (biasParameter_.get() != NULL) { + biases_ = std::unique_ptr(new Weight(1, getSize(), biasParameter_)); + } + + return true; + } + +实现前向传播的部分有下面几个步骤。 + +- 每个层在其 :code:`forward` 函数的开头必须调用 :code:`Layer::forward(passType);` 。 +- 之后使用 :code:`reserveOutput(batchSize, size);` 为输出分配内存。由于我们支持训练数据有不同的批次大小,所以这一步是必要的。 :code:`reserveOutput` 会相应地改变输出的尺寸。为了保证效率,如果需要扩大矩阵,我们会重新分配内存;如果需要缩减矩阵,我们会继续使用现有的内存块。 +- 之后使用矩阵运算函数来计算 :math:`\sum_i W_i x + b`。:code:`getInput(i).value` 返回第i个输入矩阵。每个输入都是一个 :math:`batchSize \times dim` 的矩阵,每行表示一个批次中的单个输入。对于我们支持的全部矩阵操作,请参考 :code:`paddle/math/Matrix.h`和:code:`paddle/math/BaseMatrix.h` 。 +- 最终,使用 :code:`forwardActivation();` 进行激活操作。这会自动进行网络配置中声明的激活操作。 + + +.. code-block:: c++ + + void FullyConnectedLayer::forward(PassType passType) { + Layer::forward(passType); + + /* 若有必要,为output_申请内存 */ + int batchSize = getInput(0).getBatchSize(); + int size = getSize(); + + { + // 设置输出的尺寸 + reserveOutput(batchSize, size); + } + + MatrixPtr outV = getOutputValue(); + + // 对每个输入乘上变换矩阵 + for (size_t i = 0; i != inputLayers_.size(); ++i) { + auto input = getInput(i); + CHECK(input.value) << "The input of 'fc' layer must be matrix"; + i == 0 ? outV->mul(input.value, weights_[i]->getW(), 1, 0) + : outV->mul(input.value, weights_[i]->getW(), 1, 1); + } + + /* 加上偏置向量 */ + if (biases_.get() != NULL) { + outV->addBias(*(biases_->getW()), 1); + } + + /* 激活 */ { + forwardActivation(); + } + } + +实现后向传播的部分有下面几个步骤。 + +- :code:`backwardActivation()` 计算激活函数的梯度。通过 :code:`getOutputGrad()` 来获得输出的梯度,调用该函数后,梯度会就地(不使用额外空间)乘上输出的梯度。 +- 计算偏置的梯度。注意,我们使用 :code:`biases_->getWGrad()` 来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用 :code:`getParameterPtr()->incUpdate(callback);` 。这用于在多线程和多机上更新参数。 +- 最后,计算转换矩阵和输入的梯度,并对相应的参数调用 :code:`incUpdate` 。PaddlePaddle可以通过该机制判断是否已经收集齐所有的梯度,从而可以做一些与计算重叠的工作(例如,网络通信)。 + + +.. code-block:: c++ + + void FullyConnectedLayer::backward(const UpdateCallback& callback) { + /* 对激活求导 */ { + backwardActivation(); + } + + if (biases_ && biases_->getWGrad()) { + biases_->getWGrad()->collectBias(*getOutputGrad(), 1); + + biases_->getParameterPtr()->incUpdate(callback); + } + + bool syncFlag = hl_get_sync_flag(); + + for (size_t i = 0; i != inputLayers_.size(); ++i) { + /* 计算当前层权重的梯度 */ + if (weights_[i]->getWGrad()) { + MatrixPtr input_T = getInputValue(i)->getTranspose(); + MatrixPtr oGrad = getOutputGrad(); + { + weights_[i]->getWGrad()->mul(input_T, oGrad, 1, 1); + } + } + + + /* 计算输入层的偏差 */ + MatrixPtr preGrad = getInputGrad(i); + if (NULL != preGrad) { + MatrixPtr weights_T = weights_[i]->getW()->getTranspose(); + preGrad->mul(getOutputGrad(), weights_T, 1, 1); + } + + { + weights_[i]->getParameterPtr()->incUpdate(callback); + } + } + } + + :code:`prefetch` 函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。使用远程稀疏方式训练时,完整的参数矩阵被分布在不同的参数服务器上。当网络层用一个批次做训练时,该批次的输入中仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的变换矩阵的那些行。 :code:`prefetch` 表明了这些行的标号。 + +大多数层不需要远程稀疏训练函数。这种情况下不需要重写该函数。 + +.. code-block:: c++ + + void FullyConnectedLayer::prefetch() { + for (size_t i = 0; i != inputLayers_.size(); ++i) { + auto* sparseParam = + dynamic_cast(weights_[i]->getW().get()); + if (sparseParam) { + MatrixPtr input = getInputValue(i); + sparseParam->addRows(input); + } + } + } + +最后,使用 :code:`REGISTER_LAYER(fc, FullyConnectedLayer);` 来注册该层。 :code:`fc` 是该层的标识符, :code:`FullyConnectedLayer` 是该层的类名。 + +.. code-block:: c++ + + namespace paddle { + REGISTER_LAYER(fc, FullyConnectedLayer); + } + +若 :code:`cpp` 被放在 :code:`paddle/gserver/layers` 目录下,其会自动被加入编译列表。 + + +写梯度检查单元测试 +=============================== + +写梯度检查单元测试是一个验证新实现的层是否正确的相对简单的办法。梯度检查单元测试通过有限差分法来验证一个层的梯度。首先对输入做一个小的扰动 :math:`\Delta x` ,然后观察到输出的变化为 :math:`\Delta y` ,那么,梯度就可以通过这个方程计算得到 :math:`\frac{\Delta y}{\Delta x }` 。之后,再用这个梯度去和 :code:`backward` 函数得到的梯度去对比,以保证梯度计算的正确性。需要注意的是梯度检查仅仅验证了梯度的计算,并不保证 :code:`forward` 和 :code:`backward` 函数的实现是正确的。你需要一些更复杂的单元测试来保证你实现的网络层是正确的。 + +所有网络层的梯度检查单测都位于 :code:`paddle/gserver/tests/test_LayerGrad.cpp` 。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: + ++ 生成网络层配置。网络层配置包含以下几项: + - 偏置参数的大小。(例子中是4096) + - 层的类型。(例子中是fc) + - 层的大小。(例子中是4096) + - 激活的类型。(例子中是softmax) + - dropout的比例。(例子中是0.1) ++ 配置网络层的输入。在这个例子里,我们仅有一个输入。 + - 输入的类型( :code:`INPUT_DATA` ),可以是以下几种: + - :code:`INPUT_DATA` :稠密向量。 + - :code:`INPUT_LABEL` :整数。 + - :code:`INPUT_DATA_TARGET` :稠密向量,但不用于计算梯度。 + - :code:`INPUT_SEQUENCE_DATA` :含有序列信息的稠密向量。 + - :code:`INPUT_HASSUB_SEQUENCE_DATA` :含有序列信息和子序列信息的稠密向量。 + - :code:`INPUT_SEQUENCE_LABEL` :含有序列信息的整数。 + - :code:`INPUT_SPARSE_NON_VALUE_DATA` :0-1稀疏数据。 + - :code:`INPUT_SPARSE_FLOAT_VALUE_DATA` :浮点稀疏数据。 + - 输入的名字。(例子中是 :code:`layer_0` ) + - 输入的大小。(例子中是8192) + - 非零数字的个数,仅对稀疏数据有效。 + - 稀疏数据的格式,仅对稀疏数据有效。 ++ 对每个输入,都需要调用一次 :code:`config.layerConfig.add_inputs();` 。 ++ 调用 :code:`testLayerGrad` 来做梯度检查。它包含以下参数。 + - 层和输入的配置。(例子中是 :code:`config` ) + - 网络层的类型。(例子中是 :code:`fc` ) + - 梯度检查的输入数据的批次大小。(例子中是100) + - 输入是否是转置的。大多数层需要设置为 :code:`false` 。(例子中是 :code:`false` ) + - 是否使用权重。有些层或者激活需要做归一化以保证它们的输出的和是一个常数。例如,softmax激活的输出的和总是1。在这种情况下,我们不能通过常规的梯度检查的方式来计算梯度。因此我们采用输出的加权和(非常数)来计算梯度。(例子中是 :code:`true` ,因为全连接层的激活可以是softmax) + +.. code-block:: c++ + + void testFcLayer(string format, size_t nnz) { + // Create layer configuration. + TestConfig config; + config.biasSize = 4096; + config.layerConfig.set_type("fc"); + config.layerConfig.set_size(4096); + config.layerConfig.set_active_type("softmax"); + config.layerConfig.set_drop_rate(0.1); + // Setup inputs. + config.inputDefs.push_back( + {INPUT_DATA, "layer_0", 8192, nnz, ParaSparse(format)}); + config.layerConfig.add_inputs(); + LOG(INFO) << config.inputDefs[0].sparse.sparse << " " + << config.inputDefs[0].sparse.format; + for (auto useGpu : {false, true}) { + testLayerGrad(config, "fc", 100, /* trans */ false, useGpu, + /* weight */ true); + } + } + +如果你要为了测试而增加新的文件,例如 :code:`paddle/gserver/tests/testFCGrad.cpp` ,你需要把该文件加入 :code:`paddle/gserver/tests/CMakeLists.txt` 中。下面给出了一个例子。当你执行命令 :code:`make tests` 时,所有的单测都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单测正确执行。你需要在配置cmake时将 :code:`WITH_DOUBLE` 设置为 `ON` 。 + +.. code-block:: bash + + add_unittest_without_exec(test_FCGrad + test_FCGrad.cpp + LayerGradUtil.cpp + TestUtil.cpp) + + add_test(NAME test_FCGrad + COMMAND test_FCGrad) + + +实现python封装 +======================== + +python封装的实现使得我们可以在配置文件中使用新实现的网络层。所有的python封装都在 :code:`python/paddle/trainer/config_parser.py` 中。全连接层python封装的例子中包含下面几步: + +- 所有的Python封装都使用 :code:`@config_layer('fc')` 这样的装饰器。网络层的标识符为 :code:`fc` 。 +- 实现构造函数 :code:`__init__` 。 + - 它首先调用基构造函数 :code:`super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs)` 。 :code:`FCLayer` 是Python封装的类名。 :code:`fc` 是网络层的标识符。为了封装能够正确工作,这些名字必须要写对。 + - 之后,计算变换矩阵的大小和格式(是否稀疏)。 + +.. code-block:: python + + @config_layer('fc') + class FCLayer(LayerBase): + def __init__( + self, + name, + size, + inputs, + bias=True, + **xargs): + super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs) + for input_index in xrange(len(self.inputs)): + input_layer = self.get_input_layer(input_index) + psize = self.config.size * input_layer.size + dims = [input_layer.size, self.config.size] + format = self.inputs[input_index].format + sparse = format == "csr" or format == "csc" + if sparse: + psize = self.inputs[input_index].nnz + self.create_input_parameter(input_index, psize, dims, sparse, format) + self.create_bias_parameter(bias, self.config.size) + +在网络配置中,网络层的细节可以通过下面这些代码片段来指定。这个类的参数包括: + +- :code:`name` 是网络层实例的名字标识符。 +- :code:`type` 是网络层的类型,通过网络层的标识符来指定。 +- :code:`size` 是网络层输出的大小。 +- :code:`bias` 表明这个层的一个实例是否需要偏置。 +- :code:`inputs` 说明这个层的输入,输入是由一个list中的网络层实例的名字组成的。 + +.. code-block:: python + + Layer( + name = "fc1", + type = "fc", + size = 64, + bias = True, + inputs = [Input("pool3")] + ) + +我们建议你为你的Python封装实现一个“助手”,使得搭模型时更方便。具体可以参考 :code:`python/paddle/trainer_config_helpers/layers.py` 。 From 41d1765db88de67039ea3226a812b4f207673ac0 Mon Sep 17 00:00:00 2001 From: Zhizhong Su Date: Wed, 4 Jan 2017 13:35:01 +0000 Subject: [PATCH 092/119] a missing character in line 32 --- doc/howto/dev/new_layer_cn.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/howto/dev/new_layer_cn.rst b/doc/howto/dev/new_layer_cn.rst index 897a8be5b3..9489a921c7 100644 --- a/doc/howto/dev/new_layer_cn.rst +++ b/doc/howto/dev/new_layer_cn.rst @@ -29,7 +29,7 @@ 其中 :math:`f(.)` 是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。 -变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传根据输出的梯度,分别计算每个参数的梯度,以及输入的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 +变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传播根据输出的梯度,分别计算每个参数的梯度,以及输入的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 假设损失函数是 :math:`c(y)` ,那么 From 8acd1ac31a741558725bebe1c97fcce7e28bdef5 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 00:23:23 +0800 Subject: [PATCH 093/119] Add extern python interp --- paddle/api/CMakeLists.txt | 32 ++++++++++++++++++- paddle/api/test/CMakeLists.txt | 2 +- paddle/api/test/run_tests.sh | 10 +++--- paddle/scripts/travis/build_and_test.sh | 9 ++---- paddle/setup.py.in | 2 ++ paddle/trainer/tests/CMakeLists.txt | 5 +-- paddle/trainer/tests/test_Trainer.cpp | 5 --- paddle/utils/.gitignore | 1 + paddle/utils/CMakeLists.txt | 2 ++ .../{PythonUtil.cpp => PythonUtil.cpp.in} | 2 ++ proto/CMakeLists.txt | 4 +-- python/CMakeLists.txt | 6 +--- .../tests/CMakeLists.txt | 8 ++--- .../tests/configs/generate_protostr.sh | 8 ++--- .../tests/configs/run_tests.sh | 2 +- 15 files changed, 62 insertions(+), 36 deletions(-) rename paddle/utils/{PythonUtil.cpp => PythonUtil.cpp.in} (98%) diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index da6dad10cd..dd617e3268 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -1,3 +1,21 @@ +FUNCTION(generate_python_api target_name) + ADD_CUSTOM_COMMAND(OUTPUT ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py + ${PROJ_ROOT}/paddle/Paddle_wrap.cxx + ${PROJ_ROOT}/paddle/Paddle_wrap.h + COMMAND ${SWIG_EXECUTABLE} -python -c++ -outcurrentdir -I../ api/Paddle.swig + && mv ${PROJ_ROOT}/paddle/swig_paddle.py ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py + DEPENDS ${PROJ_ROOT}/paddle/api/Paddle.swig + ${PROJ_ROOT}/paddle/api/PaddleAPI.h + ${external_project_dependencies} + WORKING_DIRECTORY ${PROJ_ROOT}/paddle + COMMENT "Generate Python API from swig") + ADD_CUSTOM_TARGET(${target_name} ALL DEPENDS + ${PROJ_ROOT}/paddle/Paddle_wrap.cxx + ${PROJ_ROOT}/paddle/Paddle_wrap.h + ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py + ${external_project_dependencies}) +ENDFUNCTION(generate_python_api) + set(API_SOURCES Arguments.cpp ConfigParser.cpp @@ -42,7 +60,7 @@ file(GLOB PY_PADDLE_PYTHON_FILES ${PROJ_ROOT}/paddle/py_paddle/*.py) # TODO(yuyang18) : make wheel name calculated by cmake add_custom_command(OUTPUT ${PROJ_ROOT}/paddle/dist/.timestamp - COMMAND ${PYTHON_EXECUTABLE} setup.py bdist_wheel + COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch dist/.timestamp COMMAND rm -rf py_paddle.egg-info build WORKING_DIRECTORY ${PROJ_ROOT}/paddle @@ -76,5 +94,17 @@ add_dependencies(python_api_wheel python_swig_sources paddle_cuda) if(WITH_TESTING) + SET(PIP_SOURCES_DIR ${PYTHON_SOURCES_DIR}/pip) + ExternalProject_Add(pip + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY https://github.com/pypa/pip.git + GIT_TAG 9.0.1 + PREFIX ${PIP_SOURCES_DIR} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools python_api_wheel + ) add_subdirectory(test) endif() diff --git a/paddle/api/test/CMakeLists.txt b/paddle/api/test/CMakeLists.txt index 08a0fe96a0..985df6f56e 100644 --- a/paddle/api/test/CMakeLists.txt +++ b/paddle/api/test/CMakeLists.txt @@ -1,2 +1,2 @@ add_test(NAME test_swig_api - COMMAND bash ${PROJ_ROOT}/paddle/api/test/run_tests.sh) + COMMAND bash ${PROJ_ROOT}/paddle/api/test/run_tests.sh ${PYTHON_EXECUTABLE} ${PYTHON_INSTALL_DIR}/bin/pip) diff --git a/paddle/api/test/run_tests.sh b/paddle/api/test/run_tests.sh index 2f12ba0264..f00ec2c967 100755 --- a/paddle/api/test/run_tests.sh +++ b/paddle/api/test/run_tests.sh @@ -20,11 +20,11 @@ popd > /dev/null cd $SCRIPTPATH -rm -rf .test_env -virtualenv .test_env -source .test_env/bin/activate +# rm -rf .test_env +# virtualenv .test_env +# source .test_env/bin/activate -pip --timeout 600 install ../../dist/*.whl +$1 -m pip install ../../dist/*.whl test_list="testArguments.py testGradientMachine.py testMatrix.py testVector.py testTrain.py testTrainer.py" @@ -33,7 +33,7 @@ export PYTHONPATH=$PWD/../../../python/ for fn in $test_list do echo "test $fn" - python $fn + $1 $fn if [ $? -ne 0 ]; then exit 1 fi diff --git a/paddle/scripts/travis/build_and_test.sh b/paddle/scripts/travis/build_and_test.sh index 9caeb21beb..fb21712188 100755 --- a/paddle/scripts/travis/build_and_test.sh +++ b/paddle/scripts/travis/build_and_test.sh @@ -1,15 +1,13 @@ #!/bin/bash -./build_submodules.sh source ./common.sh -CMAKE_EXTRA="" + if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - CMAKE_EXTRA="-DPYTHON_LIBRARY=/usr/local/Cellar/python/2.7.12_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config/libpython2.7.dylib" + CMAKE_EXTRA="-DWITH_SWIG_PY=OFF" else CMAKE_EXTRA="-DWITH_SWIG_PY=ON" fi - -cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_TESTING=ON -DON_TRAVIS=ON -DON_COVERALLS=ON ${CMAKE_EXTRA} +cmake .. -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_TESTING=ON -DON_TRAVIS=ON -DON_COVERALLS=ON ${CMAKE_EXTRA} NPROC=1 if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then @@ -22,6 +20,5 @@ elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then env CTEST_OUTPUT_ON_FAILURE=1 make test ARGS="-j $NPROC" fi - sudo make install sudo paddle version diff --git a/paddle/setup.py.in b/paddle/setup.py.in index 464ad63286..e3650bf1c0 100644 --- a/paddle/setup.py.in +++ b/paddle/setup.py.in @@ -14,7 +14,9 @@ # This file is used to build paddle python binding package. # It will be invoked by Makefile that generated by COMAKE + from setuptools import setup, Extension + import numpy as np import api.paddle_ld_flags import platform diff --git a/paddle/trainer/tests/CMakeLists.txt b/paddle/trainer/tests/CMakeLists.txt index 28c3d6f263..22e07bd0e9 100644 --- a/paddle/trainer/tests/CMakeLists.txt +++ b/paddle/trainer/tests/CMakeLists.txt @@ -17,9 +17,10 @@ add_test(NAME test_Compare ################# test_Trainer ########################### add_unittest_without_exec(test_Trainer test_Trainer.cpp) -set(diy_dll_dir ${CMAKE_CURRENT_BINARY_DIR}/../../gserver/tests) add_test(NAME test_Trainer COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python/ + ${PYTHON_EXECUTABLE} ${PROJ_ROOT}/paddle/trainer/tests/gen_proto_data.py && + ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python/ ${CMAKE_CURRENT_BINARY_DIR}/test_Trainer WORKING_DIRECTORY ${PROJ_ROOT}/paddle/) @@ -82,5 +83,5 @@ add_test(NAME test_PyDataProviderWrapper #################### test_config_parser ######################### add_test(NAME test_config_parser COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python/ - python ${PROJ_ROOT}/paddle/trainer/tests/config_parser_test.py + ${PYTHON_EXECUTABLE} ${PROJ_ROOT}/paddle/trainer/tests/config_parser_test.py WORKING_DIRECTORY ${PROJ_ROOT}/paddle/) diff --git a/paddle/trainer/tests/test_Trainer.cpp b/paddle/trainer/tests/test_Trainer.cpp index 371282dd6b..264bc46ebc 100644 --- a/paddle/trainer/tests/test_Trainer.cpp +++ b/paddle/trainer/tests/test_Trainer.cpp @@ -96,11 +96,6 @@ TEST(checkGradient, multi) { TEST(checkGradient, hsigmoid) { checkGradientTest(configFile2, false, false); } TEST(checkGradient, chunk) { -#if defined(__APPLE__) || defined(__OSX__) - EXPECT_EQ(0, system("python trainer/tests/gen_proto_data.py")); -#else - EXPECT_EQ(0, system("python2 trainer/tests/gen_proto_data.py")); -#endif checkGradientTest(configFile3, false, false); #ifndef PADDLE_ONLY_CPU checkGradientTest(configFile3, true, true); diff --git a/paddle/utils/.gitignore b/paddle/utils/.gitignore index f2cfd74094..956b606a18 100644 --- a/paddle/utils/.gitignore +++ b/paddle/utils/.gitignore @@ -1 +1,2 @@ enable_virtualenv.c +PythonUtil.cpp diff --git a/paddle/utils/CMakeLists.txt b/paddle/utils/CMakeLists.txt index 45240b5002..10d906ee16 100644 --- a/paddle/utils/CMakeLists.txt +++ b/paddle/utils/CMakeLists.txt @@ -1,5 +1,7 @@ # The utilities for paddle +configure_file(PythonUtil.cpp.in ${PROJ_ROOT}/paddle/utils/PythonUtil.cpp) + file(GLOB UTIL_HEADERS . *.h) file(GLOB UTIL_SOURCES . *.cpp) create_resources(enable_virtualenv.py enable_virtualenv.c) diff --git a/paddle/utils/PythonUtil.cpp b/paddle/utils/PythonUtil.cpp.in similarity index 98% rename from paddle/utils/PythonUtil.cpp rename to paddle/utils/PythonUtil.cpp.in index 7faeff55c2..e0caaf4cd6 100644 --- a/paddle/utils/PythonUtil.cpp +++ b/paddle/utils/PythonUtil.cpp.in @@ -195,6 +195,8 @@ extern const char enable_virtualenv_py[]; } void initPython(int argc, char** argv) { #ifndef PADDLE_NO_PYTHON + char PythonHome[] = "@PYTHON_INSTALL_DIR@"; // NOLINT + Py_SetPythonHome(PythonHome); Py_SetProgramName(argv[0]); Py_Initialize(); PySys_SetArgv(argc, argv); diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index c4e170b10f..e854b2b427 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -18,7 +18,7 @@ foreach(filename ${proto_filenames}) ${PROTO_GEN} ${CUR_PROTO_GEN}) add_custom_command(OUTPUT ${CUR_PROTO_GEN} - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + COMMAND env ${py_env} ${PROTOBUF_PROTOC_EXECUTABLE} --cpp_out ${CMAKE_CURRENT_BINARY_DIR} --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} DEPENDS ${filename} ${external_project_dependencies}) @@ -29,7 +29,7 @@ foreach(filename ${proto_filenames}) ${CUR_PROTO_GEN_PY} ${PROTO_GEN_PY}) add_custom_command(OUTPUT ${CUR_PROTO_GEN_PY} - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --python_out ${PROJ_ROOT}/python/paddle/proto + COMMAND env ${py_env} ${PROTOBUF_PROTOC_EXECUTABLE} --python_out ${PROJ_ROOT}/python/paddle/proto --proto_path ${PROJ_ROOT}/proto ${PROJ_ROOT}/proto/${filename} DEPENDS ${filename} ${external_project_dependencies}) endforeach() diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 6b80e4d58e..0a3599a47a 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -14,17 +14,13 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp - COMMAND ${PYTHON_EXECUTABLE} setup.py bdist_wheel + COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp DEPENDS gen_proto_py ${PY_FILES}) add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) -find_python_module(pip REQUIRED) -find_python_module(wheel REQUIRED) -find_python_module(google.protobuf REQUIRED) - add_subdirectory(paddle/trainer_config_helpers/tests) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dist/ diff --git a/python/paddle/trainer_config_helpers/tests/CMakeLists.txt b/python/paddle/trainer_config_helpers/tests/CMakeLists.txt index d1a9843d32..403aafabe9 100644 --- a/python/paddle/trainer_config_helpers/tests/CMakeLists.txt +++ b/python/paddle/trainer_config_helpers/tests/CMakeLists.txt @@ -1,12 +1,12 @@ #################### test_config_parser ######################### add_test(NAME layers_test COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python/ - python ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/layers_test.py + ${PYTHON_EXECUTABLE} ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/layers_test.py WORKING_DIRECTORY ${PROJ_ROOT}/python/paddle) add_test(NAME test_reset_hook COMMAND ${PROJ_ROOT}/paddle/.set_python_path.sh -d ${PROJ_ROOT}/python/ - python ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/test_reset_hook.py + ${PYTHON_EXECUTABLE} ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/test_reset_hook.py WORKING_DIRECTORY ${PROJ_ROOT}/python/paddle) if (PROTOBUF_3) @@ -14,12 +14,12 @@ if (PROTOBUF_3) ProtobufEqualMain.cpp) add_test(NAME test_layerHelpers COMMAND - ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh + ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/protobuf_equal ) else() add_test(NAME test_layerHelpers COMMAND - ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh + ${PROJ_ROOT}/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh ${PYTHON_EXECUTABLE} ) endif() diff --git a/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh b/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh index a54af94ce3..ee5961af75 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/generate_protostr.sh @@ -10,13 +10,13 @@ protostr=$PWD/protostr for conf in ${configs[*]} do echo "Generating " $conf - python -m paddle.utils.dump_config $conf.py > $protostr/$conf.protostr.unittest - cat ${conf}.py |python test_config_parser_for_non_file_config.py > $protostr/$conf.protostr.non_file_config.unittest + $1 -m paddle.utils.dump_config $conf.py > $protostr/$conf.protostr.unittest + cat ${conf}.py |$1 test_config_parser_for_non_file_config.py > $protostr/$conf.protostr.non_file_config.unittest done for conf in ${whole_configs[*]} do echo "Generating " $conf - python -m paddle.utils.dump_config $conf.py "" --whole > $protostr/$conf.protostr.unittest - cat ${conf}.py |python test_config_parser_for_non_file_config.py --whole > $protostr/$conf.protostr.non_file_config.unittest + $1 -m paddle.utils.dump_config $conf.py "" --whole > $protostr/$conf.protostr.unittest + cat ${conf}.py |$1 test_config_parser_for_non_file_config.py --whole > $protostr/$conf.protostr.non_file_config.unittest done diff --git a/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh b/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh index e984ee7062..a37eb6439e 100755 --- a/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh +++ b/python/paddle/trainer_config_helpers/tests/configs/run_tests.sh @@ -7,7 +7,7 @@ protostr=`dirname $0`/protostr files=`ls $protostr | grep -v "unittest"` -./generate_protostr.sh +./generate_protostr.sh $1 . ./file_list.sh From 9e7f2b8de830335cb2d645cf993c8dd82b6450cc Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 00:24:08 +0800 Subject: [PATCH 094/119] Add system configure --- cmake/configure.cmake | 64 +++++++++++++++++++++++++++++++++++++++++++ cmake/system.cmake | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 cmake/configure.cmake create mode 100644 cmake/system.cmake diff --git a/cmake/configure.cmake b/cmake/configure.cmake new file mode 100644 index 0000000000..ae0ec01d94 --- /dev/null +++ b/cmake/configure.cmake @@ -0,0 +1,64 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if(WITH_DSO) + add_definitions(-DPADDLE_USE_DSO) +endif(WITH_DSO) + +if(WITH_DOUBLE) + add_definitions(-DPADDLE_TYPE_DOUBLE) +endif(WITH_DOUBLE) + +if(NOT WITH_TIMER) + add_definitions(-DPADDLE_DISABLE_TIMER) +endif(NOT WITH_TIMER) + +if(NOT WITH_PROFILER) + add_definitions(-DPADDLE_DISABLE_PROFILER) +endif(NOT WITH_PROFILER) + +if(NOT WITH_GPU) + add_definitions(-DPADDLE_ONLY_CPU) + add_definitions(-DHPPL_STUB_FUNC) + + list(APPEND CMAKE_CXX_SOURCE_FILE_EXTENSIONS cu) +else() + FIND_PACKAGE(CUDA REQUIRED) + + if(${CUDA_VERSION_MAJOR} VERSION_LESS 7) + message(FATAL_ERROR "Paddle need CUDA >= 7.0 to compile") + endif() + + if(NOT CUDNN_FOUND) + message(FATAL_ERROR "Paddle need cudnn to compile") + endif() + + if(WITH_AVX) + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} "-Xcompiler ${AVX_FLAG}") + else(WITH_AVX) + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} "-Xcompiler ${SSE3_FLAG}") + endif(WITH_AVX) + + # Include cuda and cudnn + include_directories(${CUDNN_INCLUDE_DIR}) + include_directories(${CUDA_TOOLKIT_INCLUDE}) +endif(NOT WITH_GPU) + +if(WITH_AVX) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${AVX_FLAG}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${AVX_FLAG}") +else(WITH_AVX) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SSE3_FLAG}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SSE3_FLAG}") +endif(WITH_AVX) diff --git a/cmake/system.cmake b/cmake/system.cmake new file mode 100644 index 0000000000..788db404eb --- /dev/null +++ b/cmake/system.cmake @@ -0,0 +1,53 @@ +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +IF(WIN32) + SET(HOST_SYSTEM "win32") +ELSE(WIN32) + IF(APPLE) + EXEC_PROGRAM (sw_vers ARGS -productVersion OUTPUT_VARIABLE MACOSX_VERSION) + STRING(REGEX MATCH "[0-9]+.[0-9]+" VERSION "${MACOSX_VERSION}") + SET(MACOS_VERSION ${VERSION}) + SET(HOST_SYSTEM "macosx") + ELSE(APPLE) + IF(EXISTS "/etc/issue") + FILE(READ "/etc/issue" LINUX_ISSUE) + IF(LINUX_ISSUE MATCHES "CentOS") + SET(HOST_SYSTEM "centos") + ELSEIF(LINUX_ISSUE MATCHES "Debian") + SET(HOST_SYSTEM "debian") + ELSEIF(LINUX_ISSUE MATCHES "Ubuntu") + SET(HOST_SYSTEM "ubuntu") + ENDIF() + ENDIF(EXISTS "/etc/issue") + ENDIF(APPLE) +ENDIF(WIN32) + +# query number of logical cores +CMAKE_HOST_SYSTEM_INFORMATION(RESULT CPU_CORES QUERY NUMBER_OF_LOGICAL_CORES) + +MARK_AS_ADVANCED(HOST_SYSTEM CPU_CORES) + +MESSAGE(STATUS "Found Paddle host system: ${HOST_SYSTEM}") +MESSAGE(STATUS "Found Paddle host system's CPU: ${CPU_CORES} cores") + +# external dependencies log output +SET(EXTERNAL_PROJECT_LOG_ARGS + LOG_DOWNLOAD 0 # Wrap download in script to log output + LOG_UPDATE 1 # Wrap update in script to log output + LOG_CONFIGURE 1 # Wrap configure in script to log output + LOG_BUILD 1 # Wrap build in script to log output + LOG_TEST 1 # Wrap test in script to log output + LOG_INSTALL 1 # Wrap install in script to log output +) From e2d0e09a8c92de7faf7d36bfcc0bdbeeda9184e0 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 00:25:04 +0800 Subject: [PATCH 095/119] Update external dependencies --- cmake/external/gflags.cmake | 23 ++-- cmake/external/glog.cmake | 24 ++-- cmake/external/gtest.cmake | 64 ++++----- cmake/external/numpy.cmake | 78 ----------- cmake/external/openblas.cmake | 50 +++---- cmake/external/protobuf.cmake | 51 +++---- cmake/external/python.cmake | 242 ++++++++++++++++++++++++++-------- cmake/external/swig.cmake | 124 +++++++---------- cmake/external/warpctc.cmake | 40 +++--- cmake/external/zlib.cmake | 27 ++-- 10 files changed, 379 insertions(+), 344 deletions(-) delete mode 100644 cmake/external/numpy.cmake diff --git a/cmake/external/gflags.cmake b/cmake/external/gflags.cmake index 55f9a4c3e6..d38b7d1ba2 100644 --- a/cmake/external/gflags.cmake +++ b/cmake/external/gflags.cmake @@ -15,26 +15,25 @@ INCLUDE(ExternalProject) SET(GFLAGS_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gflags) -SET(GFLAGS_INSTALL_DIR ${PROJECT_BINARY_DIR}/gflags) +SET(GFLAGS_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/gflags) +SET(GFLAGS_INCLUDE_DIR "${GFLAGS_INSTALL_DIR}/include" CACHE PATH "gflags include directory." FORCE) +IF(WIN32) + set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/gflags.lib" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE) +ELSE(WIN32) + set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/libgflags.a" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE) +ENDIF(WIN32) + +INCLUDE_DIRECTORIES(${GFLAGS_INCLUDE_DIR}) ExternalProject_Add( gflags + ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY "https://github.com/gflags/gflags.git" PREFIX ${GFLAGS_SOURCES_DIR} + UPDATE_COMMAND "" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${GFLAGS_INSTALL_DIR} CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON CMAKE_ARGS -DBUILD_TESTING=OFF - LOG_DOWNLOAD =ON - UPDATE_COMMAND "" ) -SET(GFLAGS_INCLUDE_DIR "${GFLAGS_INSTALL_DIR}/include" CACHE PATH "gflags include directory." FORCE) -INCLUDE_DIRECTORIES(${GFLAGS_INCLUDE_DIR}) - -IF(WIN32) - set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/gflags.lib" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE) -ELSE(WIN32) - set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/libgflags.a" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE) -ENDIF(WIN32) - LIST(APPEND external_project_dependencies gflags) diff --git a/cmake/external/glog.cmake b/cmake/external/glog.cmake index 473071a72a..bec69f3ddf 100644 --- a/cmake/external/glog.cmake +++ b/cmake/external/glog.cmake @@ -15,27 +15,27 @@ INCLUDE(ExternalProject) SET(GLOG_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/glog) -SET(GLOG_INSTALL_DIR ${PROJECT_BINARY_DIR}/glog) +SET(GLOG_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/glog) +SET(GLOG_INCLUDE_DIR "${GLOG_INSTALL_DIR}/include" CACHE PATH "glog include directory." FORCE) + +IF(WIN32) + SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.lib" CACHE FILEPATH "glog library." FORCE) +ELSE(WIN32) + SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.a" CACHE FILEPATH "glog library." FORCE) +ENDIF(WIN32) + +INCLUDE_DIRECTORIES(${GLOG_INCLUDE_DIR}) ExternalProject_Add( glog + ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY "https://github.com/google/glog.git" PREFIX ${GLOG_SOURCES_DIR} + UPDATE_COMMAND "" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${GLOG_INSTALL_DIR} CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON CMAKE_ARGS -DWITH_GFLAGS=OFF CMAKE_ARGS -DBUILD_TESTING=OFF - LOG_DOWNLOAD =ON - UPDATE_COMMAND "" ) -SET(GLOG_INCLUDE_DIR "${GLOG_INSTALL_DIR}/include" CACHE PATH "glog include directory." FORCE) -INCLUDE_DIRECTORIES(${GLOG_INCLUDE_DIR}) - -IF(WIN32) - SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.lib" CACHE FILEPATH "glog library." FORCE) -ELSE(WIN32) - SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.a" CACHE FILEPATH "glog library." FORCE) -ENDIF(WIN32) - LIST(APPEND external_project_dependencies glog) diff --git a/cmake/external/gtest.cmake b/cmake/external/gtest.cmake index a6ed9e9b9f..2fcb7893fa 100644 --- a/cmake/external/gtest.cmake +++ b/cmake/external/gtest.cmake @@ -12,38 +12,40 @@ # See the License for the specific language governing permissions and # limitations under the License. -INCLUDE(ExternalProject) +IF(WITH_TESTING) + ENABLE_TESTING() + INCLUDE(ExternalProject) -SET(GTEST_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gtest) -SET(GTEST_INSTALL_DIR ${PROJECT_BINARY_DIR}/gtest) + SET(GTEST_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gtest) + SET(GTEST_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/gtest) + SET(GTEST_INCLUDE_DIR "${GTEST_INSTALL_DIR}/include" CACHE PATH "gtest include directory." FORCE) -ExternalProject_Add( - gtest - GIT_REPOSITORY "https://github.com/google/googletest.git" - GIT_TAG "release-1.8.0" - PREFIX ${GTEST_SOURCES_DIR} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GTEST_INSTALL_DIR} - CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON - CMAKE_ARGS -DBUILD_GMOCK=ON - CMAKE_ARGS -Dgtest_disable_pthreads=ON - CMAKE_ARGS -Dgtest_force_shared_crt=ON - LOG_DOWNLOAD =ON - UPDATE_COMMAND "" -) + INCLUDE_DIRECTORIES(${GTEST_INCLUDE_DIR}) -SET(GTEST_INCLUDE_DIR "${GTEST_INSTALL_DIR}/include" CACHE PATH "gtest include directory." FORCE) -INCLUDE_DIRECTORIES(${GTEST_INCLUDE_DIR}) + IF(WIN32) + set(GTEST_LIBRARIES + "${GTEST_INSTALL_DIR}/lib/gtest.lib" CACHE FILEPATH "gtest libraries." FORCE) + set(GTEST_MAIN_LIBRARIES + "${GTEST_INSTALL_DIR}/lib/gtest_main.lib" CACHE FILEPATH "gtest main libraries." FORCE) + ELSE(WIN32) + set(GTEST_LIBRARIES + "${GTEST_INSTALL_DIR}/lib/libgtest.a" CACHE FILEPATH "gtest libraries." FORCE) + set(GTEST_MAIN_LIBRARIES + "${GTEST_INSTALL_DIR}/lib/libgtest_main.a" CACHE FILEPATH "gtest main libraries." FORCE) + ENDIF(WIN32) -IF(WIN32) - set(GTEST_LIBRARIES - "${GTEST_INSTALL_DIR}/lib/gtest.lib" - "${GTEST_INSTALL_DIR}/lib/gtest_main.lib" CACHE FILEPATH "gtest libraries." FORCE) -ELSE(WIN32) - set(GTEST_LIBRARIES - "${GTEST_INSTALL_DIR}/lib/libgtest.a" - "${GTEST_INSTALL_DIR}/lib/libgtest_main.a" CACHE FILEPATH "gtest libraries." FORCE) -ENDIF(WIN32) - -ENABLE_TESTING() - -LIST(APPEND external_project_dependencies gtest) + ExternalProject_Add( + gtest + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "release-1.8.0" + PREFIX ${GTEST_SOURCES_DIR} + UPDATE_COMMAND "" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GTEST_INSTALL_DIR} + CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON + CMAKE_ARGS -DBUILD_GMOCK=ON + CMAKE_ARGS -Dgtest_disable_pthreads=ON + CMAKE_ARGS -Dgtest_force_shared_crt=ON + ) + LIST(APPEND external_project_dependencies gtest) +ENDIF(WITH_TESTING) diff --git a/cmake/external/numpy.cmake b/cmake/external/numpy.cmake deleted file mode 100644 index d01cff9722..0000000000 --- a/cmake/external/numpy.cmake +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FIND_PACKAGE(NumPy) - -IF(NOT ${NUMPY_FOUND}) - - INCLUDE(ExternalProject) - - SET(NUMPY_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/numpy) - SET(NUMPY_INSTALL_DIR ${PROJECT_BINARY_DIR}/numpy) - set(NUMPY_VERSION "v1.11.3") - - ExternalProject_Add(setuptools - PREFIX ${PYTHON_SOURCES_DIR}/setuptools - URL http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz - URL_MD5 7df2a529a074f613b509fb44feefe74e - BUILD_IN_SOURCE 1 - UPDATE_COMMAND "" - PATCH_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python zlib - ) - - ExternalProject_Add(cython - PREFIX ${PYTHON_SOURCES_DIR}/cython - GIT_REPOSITORY https://github.com/cython/cython.git - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - PATCH_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python - ) - - ExternalProject_Add(numpy - GIT_REPOSITORY https://github.com/numpy/numpy.git - GIT_TAG ${NUMPY_VERSION} - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - PREFIX ${NUMPY_SOURCES_DIR} - BUILD_COMMAND ${PYTHON_EXECUTABLE} setup.py build - INSTALL_COMMAND ${PYTHON_EXECUTABLE} setup.py install - BUILD_IN_SOURCE 1 - DEPENDS python setuptools cython - ) - - LIST(APPEND external_project_dependencies numpy) - - # find numpy include directory - FILE(WRITE ${PROJECT_BINARY_DIR}/FindNumpyPath.py - "try: import numpy; print(numpy.get_include())\nexcept:pass\n") - - EXEC_PROGRAM("${PYTHON_EXECUTABLE}" ${PROJECT_BINARY_DIR} - ARGS "FindNumpyPath.py" - OUTPUT_VARIABLE NUMPY_PATH) - - FIND_PATH(PYTHON_NUMPY_INCLUDE_DIR numpy/arrayobject.h - HINTS "${NUMPY_PATH}" "${PYTHON_INCLUDE_PATH}") - -ENDIF() - -INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) - diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 2683153b49..677999cc9f 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -12,31 +12,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -INCLUDE(ExternalProject) +INCLUDE(cblas) -SET(CBLAS_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/openblas) -SET(CBLAS_INSTALL_DIR ${PROJECT_BINARY_DIR}/openblas) +IF(NOT ${CBLAS_FOUND}) + INCLUDE(ExternalProject) -ExternalProject_Add( - openblas - GIT_REPOSITORY "https://github.com/xianyi/OpenBLAS.git" - GIT_TAG v0.2.19 - PREFIX ${CBLAS_SOURCES_DIR} - INSTALL_DIR ${CBLAS_INSTALL_DIR} - BUILD_IN_SOURCE 1 - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND cd ${CBLAS_SOURCES_DIR}/src/openblas && make -j4 - INSTALL_COMMAND cd ${CBLAS_SOURCES_DIR}/src/openblas && make install PREFIX= -) + SET(CBLAS_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/openblas) + SET(CBLAS_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/openblas) + SET(CBLAS_INC_DIR "${CBLAS_INSTALL_DIR}/include" CACHE PATH "openblas include directory." FORCE) -SET(CBLAS_INCLUDE_DIR "${CBLAS_INSTALL_DIR}/include" CACHE PATH "openblas include directory." FORCE) -INCLUDE_DIRECTORIES(${CBLAS_INCLUDE_DIR}) + IF(WIN32) + SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/openblas.lib" CACHE FILEPATH "openblas library." FORCE) + ELSE(WIN32) + SET(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/libopenblas.a" CACHE FILEPATH "openblas library" FORCE) + ENDIF(WIN32) -IF(WIN32) - set(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/openblas.lib" CACHE FILEPATH "openblas library." FORCE) -ELSE(WIN32) - set(CBLAS_LIBRARIES "${CBLAS_INSTALL_DIR}/lib/libopenblas.a" CACHE FILEPATH "openblas library" FORCE) -ENDIF(WIN32) + ExternalProject_Add( + openblas + ${EXTERNAL_PROJECT_LOG_ARGS} + URL "https://github.com/xianyi/OpenBLAS/archive/v0.2.19.tar.gz" + PREFIX ${CBLAS_SOURCES_DIR} + INSTALL_DIR ${CBLAS_INSTALL_DIR} + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND "" + BUILD_COMMAND make CC=${CMAKE_C_COMPILER} FC=${CMAKE_Fortran_COMPILER} + INSTALL_COMMAND make install PREFIX= + UPDATE_COMMAND "" + ) -LIST(APPEND external_project_dependencies openblas) + LIST(APPEND external_project_dependencies openblas) +ENDIF() + +INCLUDE_DIRECTORIES(${CBLAS_INC_DIR}) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index f42e42ef68..2f2769b4c6 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -15,24 +15,9 @@ INCLUDE(ExternalProject) SET(PROTOBUF_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/protobuf) -SET(PROTOBUF_INSTALL_DIR ${PROJECT_BINARY_DIR}/protobuf) - -ExternalProject_Add( - protobuf - PREFIX ${PROTOBUF_SOURCES_DIR} - DEPENDS zlib - GIT_REPOSITORY "https://github.com/google/protobuf.git" -# GIT_TAG "v3.1.0" - CONFIGURE_COMMAND - ${CMAKE_COMMAND} ${PROTOBUF_SOURCES_DIR}/src/protobuf/cmake - -Dprotobuf_BUILD_TESTS=OFF - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_INSTALL_PREFIX=${PROTOBUF_INSTALL_DIR} - UPDATE_COMMAND "" -) - +SET(PROTOBUF_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/protobuf) SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" CACHE PATH "protobuf include directory." FORCE) + INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) IF(WIN32) @@ -44,18 +29,34 @@ IF(WIN32) "${PROTOBUF_INSTALL_DIR}/lib/libprotoc.lib" CACHE FILEPATH "protoc library." FORCE) SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc.exe" CACHE FILEPATH "protobuf executable." FORCE) ELSE(WIN32) - FIND_PATH(PROTOBUF_LIBS_DIR libprotoc.a - ${PROTOBUF_INSTALL_DIR}/lib - ${PROTOBUF_INSTALL_DIR}/lib64 - NO_DEFAULT_PATH - ) + IF(${HOST_SYSTEM} STREQUAL "centos") + SET(LIB "lib64") + ELSE() + SET(LIB "lib") + ENDIF() SET(PROTOBUF_LITE_LIBRARY - "${PROTOBUF_LIBS_DIR}/libprotobuf-lite.a" CACHE FILEPATH "protobuf lite library." FORCE) + "${PROTOBUF_INSTALL_DIR}/${LIB}/libprotobuf-lite.a" CACHE FILEPATH "protobuf lite library." FORCE) SET(PROTOBUF_LIBRARY - "${PROTOBUF_LIBS_DIR}/libprotobuf.a" CACHE FILEPATH "protobuf library." FORCE) + "${PROTOBUF_INSTALL_DIR}/${LIB}/libprotobuf.a" CACHE FILEPATH "protobuf library." FORCE) SET(PROTOBUF_PROTOC_LIBRARY - "${PROTOBUF_LIBS_DIR}/libprotoc.a" CACHE FILEPATH "protoc library." FORCE) + "${PROTOBUF_INSTALL_DIR}/${LIB}/libprotoc.a" CACHE FILEPATH "protoc library." FORCE) SET(PROTOBUF_PROTOC_EXECUTABLE "${PROTOBUF_INSTALL_DIR}/bin/protoc" CACHE FILEPATH "protobuf executable." FORCE) ENDIF(WIN32) +ExternalProject_Add( + protobuf + ${EXTERNAL_PROJECT_LOG_ARGS} + PREFIX ${PROTOBUF_SOURCES_DIR} + UPDATE_COMMAND "" + DEPENDS zlib + GIT_REPOSITORY "https://github.com/google/protobuf.git" + GIT_TAG "9f75c5aa851cd877fb0d93ccc31b8567a6706546" + CONFIGURE_COMMAND + ${CMAKE_COMMAND} ${PROTOBUF_SOURCES_DIR}/src/protobuf/cmake + -Dprotobuf_BUILD_TESTS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_INSTALL_PREFIX=${PROTOBUF_INSTALL_DIR} +) + LIST(APPEND external_project_dependencies protobuf) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index d6cdf535fe..479ec301b7 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -12,84 +12,212 @@ # See the License for the specific language governing permissions and # limitations under the License. -FIND_PACKAGE(PythonLibs 2.7) -FIND_PACKAGE(PythonInterp 2.7) +INCLUDE(ExternalProject) -IF((NOT ${PYTHONINTERP_FOUND}) OR (NOT ${PYTHONLIBS_FOUND})) - INCLUDE(ExternalProject) +##################################### PYTHON ######################################## +SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/python) +SET(PYTHON_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/python) +SET(_python_DIR ${PYTHON_INSTALL_DIR}) - SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/Python) - SET(PYTHON_INSTALL_DIR ${PROJECT_BINARY_DIR}/Python) +IF(UNIX) + SET(PYTHON_FOUND ON) + SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include/python2.7" CACHE PATH "Python include dir" FORCE) + SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/lib/libpython2.7.a" CACHE FILEPATH "Python library" FORCE) + SET(PYTHON_EXECUTABLE ${PYTHON_INSTALL_DIR}/bin/python CACHE FILEPATH "Python executable" FORCE) + SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/lib/python2.7/site-packages" CACHE PATH "Python site-packages path" FORCE) +ELSEIF(WIN32) + SET(PYTHON_FOUND ON) + SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include" CACHE PATH "Python include dir" FORCE) + SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/libs/python27.lib" CACHE FILEPATH "Python library" FORCE) + SET(PYTHON_EXECUTABLE "${PYTHON_INSTALL_DIR}/bin/python.exe" CACHE FILEPATH "Python executable" FORCE) + SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/Lib/site-packages" CACHE PATH "Python site-packages path" FORCE) +ELSE() + MESSAGE(FATAL_ERROR "Unknown system !") +ENDIF() + +SET(py_env + PATH=${PYTHON_INSTALL_DIR}/bin/:$ENV{PATH} + PYTHONHOME=${PYTHON_INSTALL_DIR} + PYTHONPATH=${PYTHON_INSTALL_DIR}/lib:${PYTHON_INSTALL_DIR}/lib/python2.7:${PY_SITE_PACKAGES_PATH}) - IF(MSVC) - LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_ARGS - PATCH_COMMAND ${CMAKE_COMMAND} - -DPYTHON_SRC_DIR:PATH=${_python_SOURCE_DIR} - -P ${CMAKE_CURRENT_LIST_DIR}/PythonPatch.cmake - ) - ENDIF() +INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) - IF(APPLE) +IF(APPLE) LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS - -DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON - ) - ENDIF() + -DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON + ) +ENDIF() - SET(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) +SET(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) - # Force Python build to "Release". - IF(CMAKE_CONFIGURATION_TYPES) +# Force Python build to "Release". +IF(CMAKE_CONFIGURATION_TYPES) SET(SAVED_CMAKE_CFG_INTDIR ${CMAKE_CFG_INTDIR}) SET(CMAKE_CFG_INTDIR "Release") - ELSE() +ELSE() LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS - -DCMAKE_BUILD_TYPE:STRING=Release) - ENDIF() + -DCMAKE_BUILD_TYPE:STRING=Release + ) +ENDIF() - ExternalProject_Add(python +ExternalProject_Add(python + ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY "https://github.com/python-cmake-buildsystem/python-cmake-buildsystem.git" - GIT_TAG "ed5f9bcee540e47f82fa17f8360b820591aa6d66" PREFIX ${PYTHON_SOURCES_DIR} UPDATE_COMMAND "" + CMAKE_ARGS -DPYTHON_VERSION=2.7.12 + CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} CMAKE_CACHE_ARGS - -DCMAKE_INSTALL_PREFIX:PATH=${PYTHON_INSTALL_DIR} - -DBUILD_SHARED:BOOL=OFF - -DBUILD_STATIC:BOOL=ON - -DUSE_SYSTEM_LIBRARIES:BOOL=OFF - -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} - -DZLIB_INCLUDE_DIR:PATH=${ZLIB_INCLUDE_DIR} - -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} - -DDOWNLOAD_SOURCES:BOOL=ON - -DINSTALL_WINDOWS_TRADITIONAL:BOOL=OFF - ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} - ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS} + -DCMAKE_INSTALL_PREFIX:PATH=${PYTHON_INSTALL_DIR} + -DBUILD_LIBPYTHON_SHARED:BOOL=OFF + -DUSE_SYSTEM_LIBRARIES:BOOL=OFF + -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} + -DZLIB_INCLUDE_DIR:PATH=${ZLIB_INCLUDE_DIR} + -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} + -DDOWNLOAD_SOURCES:BOOL=ON + -DINSTALL_WINDOWS_TRADITIONAL:BOOL=OFF + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS} DEPENDS zlib - ) +) +#################################################################################### + +##################################### SETUPTOOLS ################################### +SET(SETUPTOOLS_SOURCES_DIR ${PYTHON_SOURCES_DIR}/setuptools) +ExternalProject_Add(setuptools + ${EXTERNAL_PROJECT_LOG_ARGS} + PREFIX ${SETUPTOOLS_SOURCES_DIR} + URL "https://pypi.python.org/packages/source/s/setuptools/setuptools-18.3.2.tar.gz" + BUILD_IN_SOURCE 1 + PATCH_COMMAND "" + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python zlib +) +##################################################################################### + +##################################### SIX ########################################### +SET(SIX_SOURCES_DIR ${PYTHON_SOURCES_DIR}/six) +ExternalProject_Add(six + ${EXTERNAL_PROJECT_LOG_ARGS} + PREFIX ${SIX_SOURCES_DIR} + URL https://pypi.python.org/packages/source/s/six/six-1.10.0.tar.gz + BUILD_IN_SOURCE 1 + PATCH_COMMAND "" + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python setuptools +) +##################################################################################### - SET(_python_DIR ${PYTHON_INSTALL_DIR}) +##################################### CYTHON ######################################## +SET(CYTHON_SOURCES_DIR ${PYTHON_SOURCES_DIR}/cython) +ExternalProject_Add(cython + ${EXTERNAL_PROJECT_LOG_ARGS} + PREFIX ${CYTHON_SOURCES_DIR} + URL https://github.com/cython/cython/archive/0.25.2.tar.gz + GIT_TAG 0.25.2 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND "" + PATCH_COMMAND "" + UPDATE_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python +) +#################################################################################### - IF(UNIX) - SET(_python_IMPORT_SUFFIX a) +##################################### NUMPY ######################################## +SET(NUMPY_SOURCES_DIR ${PYTHON_SOURCES_DIR}/numpy) +SET(NUMPY_TAG_VERSION "v1.11.3") +SET(NUMPY_VERSION "1.11.3") + +IF(WIN32) + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}.egg") +ELSE(WIN32) IF(APPLE) - SET(_python_IMPORT_SUFFIX lib) - ENDIF() - SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include/python2.7" CACHE PATH "Python include dir" FORCE) - SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/lib/libpython2.7.${_python_IMPORT_SUFFIX}" CACHE FILEPATH "Python library" FORCE) - SET(PYTHON_EXECUTABLE ${PYTHON_INSTALL_DIR}/bin/python CACHE FILEPATH "Python executable" FORCE) - SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/lib/python2.7/site-packages" CACHE PATH "Python site-packages path" FORCE) - ELSEIF(WIN32) - SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include" CACHE PATH "Python include dir" FORCE) - SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/libs/python27.lib" CACHE FILEPATH "Python library" FORCE) - SET(PYTHON_EXECUTABLE "${PYTHON_INSTALL_DIR}/bin/python.exe" CACHE FILEPATH "Python executable" FORCE) - SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/Lib/site-packages" CACHE PATH "Python site-packages path" FORCE) - ELSE() - MESSAGE(FATAL_ERROR "Unknown system !") - ENDIF() + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}-${MACOS_VERSION}-x86_64.egg") + ELSE(APPLE) + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux-x86_64.egg") + ENDIF(APPLE) +ENDIF(WIN32) -LIST(APPEND external_project_dependencies python) +SET(PYTHON_NUMPY_INCLUDE_DIR "${PY_SITE_PACKAGES_PATH}/${EGG_NAME}/numpy/core/include") +IF(${PYTHON_FOUND}) # local python + SET(PYTHON_NUMPY_INCLUDE_DIR + "${PY_SITE_PACKAGES_PATH}/${EGG_NAME}/numpy/core/include") +ELSE(${PYTHON_FOUND}) # global python + SET(PYTHON_NUMPY_INCLUDE_DIR "") + SET(PY_SITE_PACKAGES_DIR "") + FILE(WRITE ${PROJECT_BINARY_DIR}/FindNumpyPath.py + "try: import site; print(site.getsitepackages())\nexcept:pass\n") + EXEC_PROGRAM("env ${py_env} ${PYTHON_EXECUTABLE}" ${PROJECT_BINARY_DIR} + ARGS "FindNumpyPath.py" OUTPUT_VARIABLE NUMPY_PATH) -ENDIF() + STRING(REPLACE "[" "" NUMPY_PATH "${NUMPY_PATH}") + STRING(REPLACE "]" "" NUMPY_PATH "${NUMPY_PATH}") + STRING(REPLACE "'" "" NUMPY_PATH "${NUMPY_PATH}") + STRING(REPLACE ", " ";" SITE_DIRS "${NUMPY_PATH}") -INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) + FOREACH(SITE_DIR ${SITE_DIRS}) + IF(EXISTS ${SITE_DIR}) + LIST(APPEND PYTHON_NUMPY_INCLUDE_DIR + "${SITE_DIR}/${EGG_NAME}/numpy/core/include") + SET(PY_SITE_PACKAGES_DIR "${SITE_DIR}") + ENDIF() + ENDFOREACH() +ENDIF(${PYTHON_FOUND}) + +INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) + +ExternalProject_Add(numpy + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY https://github.com/numpy/numpy.git + GIT_TAG ${NUMPY_TAG_VERSION} + CONFIGURE_COMMAND "" + UPDATE_COMMAND "" + PREFIX ${NUMPY_SOURCES_DIR} + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py build + INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools cython +) +#################################################################################### + +##################################### WHEEL ######################################## +SET(WHEEL_SOURCES_DIR ${PYTHON_SOURCES_DIR}/wheel) +ExternalProject_Add(wheel + ${EXTERNAL_PROJECT_LOG_ARGS} + URL https://pypi.python.org/packages/source/w/wheel/wheel-0.29.0.tar.gz + PREFIX ${WHEEL_SOURCES_DIR} + CONFIGURE_COMMAND "" + UPDATE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools +) +#################################################################################### + +################################### PROTOBUF ####################################### +SET(PY_PROTOBUF_SOURCES_DIR ${PYTHON_SOURCES_DIR}/protobuf) +ExternalProject_Add(python-protobuf + ${EXTERNAL_PROJECT_LOG_ARGS} + URL https://pypi.python.org/packages/e0/b0/0a1b364fe8a7d177b4b7d4dca5b798500dc57a7273b93cca73931b305a6a/protobuf-3.1.0.post1.tar.gz + URL_MD5 38b5fb160c768d2f8444d0c6d637ff91 + PREFIX ${PY_PROTOBUF_SOURCES_DIR} + BUILD_IN_SOURCE 1 + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND env PATH=${PROTOBUF_INSTALL_DIR}/bin:$ENV{PATH} ${py_env} ${PYTHON_EXECUTABLE} setup.py build + INSTALL_COMMAND env PATH=${PROTOBUF_INSTALL_DIR}/bin:$ENV{PATH} ${py_env} ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python setuptools six +) +LIST(APPEND external_project_dependencies python setuptools six cython numpy wheel python-protobuf) diff --git a/cmake/external/swig.cmake b/cmake/external/swig.cmake index 2da826d375..5460b02c37 100644 --- a/cmake/external/swig.cmake +++ b/cmake/external/swig.cmake @@ -12,83 +12,59 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Look for system swig -FIND_PACKAGE(SWIG) +# build swig as an external project +INCLUDE(ExternalProject) -IF(NOT ${SWIG_FOUND}) - # build swig as an external project - INCLUDE(ExternalProject) - SET(SWIG_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/swig) - SET(SWIG_INSTALL_DIR ${PROJECT_BINARY_DIR}/swig) - SET(SWIG_TARGET_VERSION "3.0.2") - SET(SWIG_DOWNLOAD_SRC_MD5 "62f9b0d010cef36a13a010dc530d0d41") - SET(SWIG_DOWNLOAD_WIN_MD5 "3f18de4fc09ab9abb0d3be37c11fbc8f") +SET(SWIG_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/swig) +SET(SWIG_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/swig) +SET(SWIG_TARGET_VERSION "3.0.2") +SET(SWIG_DOWNLOAD_SRC_MD5 "62f9b0d010cef36a13a010dc530d0d41") +SET(SWIG_DOWNLOAD_WIN_MD5 "3f18de4fc09ab9abb0d3be37c11fbc8f") - IF(WIN32) - # swig.exe available as pre-built binary on Windows: - ExternalProject_Add(swig - URL http://prdownloads.sourceforge.net/swig/swigwin-${SWIG_TARGET_VERSION}.zip - URL_MD5 ${SWIG_DOWNLOAD_WIN_MD5} - SOURCE_DIR ${SWIG_SOURCES_DIR} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - ) - SET(SWIG_DIR ${SWIG_SOURCES_DIR} CACHE FILEPATH "SWIG Directory" FORCE) - SET(SWIG_EXECUTABLE ${SWIG_SOURCES_DIR}/swig.exe CACHE FILEPATH "SWIG Executable" FORCE) +IF(WIN32) + # swig.exe available as pre-built binary on Windows: + ExternalProject_Add(swig + URL http://prdownloads.sourceforge.net/swig/swigwin-${SWIG_TARGET_VERSION}.zip + URL_MD5 ${SWIG_DOWNLOAD_WIN_MD5} + SOURCE_DIR ${SWIG_SOURCES_DIR} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + UPDATE_COMMAND "" + ) + SET(SWIG_DIR ${SWIG_SOURCES_DIR} CACHE FILEPATH "SWIG Directory" FORCE) + SET(SWIG_EXECUTABLE ${SWIG_SOURCES_DIR}/swig.exe CACHE FILEPATH "SWIG Executable" FORCE) +ELSE(WIN32) + # From PCRE configure + ExternalProject_Add(pcre + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY https://github.com/svn2github/pcre.git + PREFIX ${SWIG_SOURCES_DIR}/pcre + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SWIG_INSTALL_DIR}/pcre + ) - ELSE(WIN32) - # From PCRE configure - ExternalProject_Add(pcre - GIT_REPOSITORY https://github.com/svn2github/pcre.git - PREFIX ${SWIG_SOURCES_DIR}/pcre - UPDATE_COMMAND "" - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SWIG_INSTALL_DIR}/pcre - ) + # swig uses bison find it by cmake and pass it down + FIND_PACKAGE(BISON) - # swig uses bison find it by cmake and pass it down - FIND_PACKAGE(BISON) + # From SWIG configure + ExternalProject_Add(swig + GIT_REPOSITORY https://github.com/swig/swig.git + GIT_TAG rel-3.0.10 + PREFIX ${SWIG_SOURCES_DIR} + CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && ./autogen.sh + CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && + env "PCRE_LIBS=${SWIG_INSTALL_DIR}/pcre/lib/libpcre.a ${SWIG_INSTALL_DIR}/pcre/lib/libpcrecpp.a ${SWIG_INSTALL_DIR}/pcre/lib/libpcreposix.a" + ./configure + --prefix=${SWIG_INSTALL_DIR} + --with-pcre-prefix=${SWIG_INSTALL_DIR}/pcre + BUILD_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make + INSTALL_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make install + UPDATE_COMMAND "" + DEPENDS pcre + ) - # From SWIG configure - ExternalProject_Add(swig - URL https://github.com/swig/swig/archive/rel-3.0.10.tar.gz - PREFIX ${SWIG_SOURCES_DIR} - UPDATE_COMMAND "" - CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && ./autogen.sh - CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && - env "PCRE_LIBS=${SWIG_INSTALL_DIR}/pcre/lib/libpcre.a \ - ${SWIG_INSTALL_DIR}/pcre/lib/libpcrecpp.a \ - ${SWIG_INSTALL_DIR}/pcre/lib/libpcreposix.a" - ./configure - --prefix=${SWIG_INSTALL_DIR} - --with-pcre-prefix=${SWIG_INSTALL_DIR}/pcre - BUILD_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make - INSTALL_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make install - DEPENDS pcre - ) + SET(SWIG_DIR ${SWIG_INSTALL_DIR}/share/swig/${SWIG_TARGET_VERSION}) + SET(SWIG_EXECUTABLE ${SWIG_INSTALL_DIR}/bin/swig) +ENDIF(WIN32) - set(SWIG_DIR ${SWIG_INSTALL_DIR}/share/swig/${SWIG_TARGET_VERSION} CACHE FILEPATH "SWIG Directory" FORCE) - set(SWIG_EXECUTABLE ${SWIG_INSTALL_DIR}/bin/swig CACHE FILEPATH "SWIG Executable" FORCE) - ENDIF(WIN32) - - LIST(APPEND external_project_dependencies swig) - -ENDIF() - -FUNCTION(generate_python_api target_name) - ADD_CUSTOM_COMMAND(OUTPUT ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py - ${PROJ_ROOT}/paddle/Paddle_wrap.cxx - ${PROJ_ROOT}/paddle/Paddle_wrap.h - COMMAND ${SWIG_EXECUTABLE} -python -c++ -outcurrentdir -I../ api/Paddle.swig - && mv ${PROJ_ROOT}/paddle/swig_paddle.py ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py - DEPENDS ${PROJ_ROOT}/paddle/api/Paddle.swig - ${PROJ_ROOT}/paddle/api/PaddleAPI.h - ${external_project_dependencies} - WORKING_DIRECTORY ${PROJ_ROOT}/paddle - COMMENT "Generate Python API from swig") - ADD_CUSTOM_TARGET(${target_name} ALL DEPENDS - ${PROJ_ROOT}/paddle/Paddle_wrap.cxx - ${PROJ_ROOT}/paddle/Paddle_wrap.h - ${PROJ_ROOT}/paddle/py_paddle/swig_paddle.py - ${external_project_dependencies}) -ENDFUNCTION(generate_python_api) +LIST(APPEND external_project_dependencies swig) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 6a88c87df6..d90768b6f1 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -15,28 +15,13 @@ INCLUDE(ExternalProject) SET(WARPCTC_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/warpctc) -SET(WARPCTC_INSTALL_DIR ${PROJECT_BINARY_DIR}/warpctc) - -IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" ) - SET(USE_OMP ON) -ELSE() - SET(USE_OMP OFF) -ENDIF() - -ExternalProject_Add( - warpctc - GIT_REPOSITORY "https://github.com/gangliao/warp-ctc.git" - PREFIX ${WARPCTC_SOURCES_DIR} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${WARPCTC_INSTALL_DIR} - CMAKE_ARGS -DWITH_GPU=${WITH_GPU} - CMAKE_ARGS -DWITH_OMP=${USE_OMP} - UPDATE_COMMAND "" -) - +SET(WARPCTC_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/warpctc) SET(WARPCTC_INCLUDE_DIR "${WARPCTC_INSTALL_DIR}/include" CACHE PATH "Warp-ctc Directory" FORCE) + INCLUDE_DIRECTORIES(${WARPCTC_INCLUDE_DIR}) SET(WARPCTC_LIB_DIR "${WARPCTC_INSTALL_DIR}/lib" CACHE PATH "Warp-ctc Library Directory" FORCE) + IF(WIN32) SET(WARPCTC_LIBRARIES "${WARPCTC_INSTALL_DIR}/lib/warpctc.dll" CACHE FILEPATH "Warp-ctc Library" FORCE) @@ -51,4 +36,23 @@ ELSE(WIN32) "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.${_warpctc_SHARED_SUFFIX}" CACHE FILEPATH "Warp-ctc Library" FORCE) ENDIF(WIN32) +IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" ) + SET(USE_OMP OFF) +ELSE() + SET(USE_OMP ON) +ENDIF() + +ExternalProject_Add( + warpctc + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY "https://github.com/gangliao/warp-ctc.git" + PREFIX ${WARPCTC_SOURCES_DIR} + UPDATE_COMMAND "" + CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${WARPCTC_INSTALL_DIR} + CMAKE_ARGS -DWITH_GPU=${WITH_GPU} + CMAKE_ARGS -DWITH_OMP=${USE_OMP} +) + LIST(APPEND external_project_dependencies warpctc) diff --git a/cmake/external/zlib.cmake b/cmake/external/zlib.cmake index ec44467aa7..916f6816aa 100644 --- a/cmake/external/zlib.cmake +++ b/cmake/external/zlib.cmake @@ -15,30 +15,29 @@ INCLUDE(ExternalProject) SET(ZLIB_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/zlib) -SET(ZLIB_INSTALL_DIR ${PROJECT_BINARY_DIR}/zlib) +SET(ZLIB_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/zlib) +SET(ZLIB_ROOT ${ZLIB_INSTALL_DIR} CACHE FILEPATH "zlib root directory." FORCE) +SET(ZLIB_INCLUDE_DIR "${ZLIB_INSTALL_DIR}/include" CACHE PATH "zlib include directory." FORCE) + +IF(WIN32) + SET(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/zlibstatic.lib" CACHE FILEPATH "zlib library." FORCE) +ELSE(WIN32) + set(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/libz.a" CACHE FILEPATH "zlib library." FORCE) +ENDIF(WIN32) + +INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR}) ExternalProject_Add( zlib + ${EXTERNAL_PROJECT_LOG_ARGS} GIT_REPOSITORY "https://github.com/madler/zlib.git" GIT_TAG "v1.2.8" PREFIX ${ZLIB_SOURCES_DIR} + UPDATE_COMMAND "" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${ZLIB_INSTALL_DIR} CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON CMAKE_ARGS -DCMAKE_MACOSX_RPATH=ON - LOG_DOWNLOAD =ON - UPDATE_COMMAND "" ) -SET(ZLIB_ROOT ${ZLIB_INSTALL_DIR} CACHE PATH "zlib root directory." FORCE) - -SET(ZLIB_INCLUDE_DIR "${ZLIB_INSTALL_DIR}/include" CACHE PATH "zlib include directory." FORCE) -INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR}) - -IF(WIN32) - SET(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/zlibstatic.lib" CACHE FILEPATH "zlib library." FORCE) -ELSE(WIN32) - set(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/libz.a" CACHE FILEPATH "zlib library." FORCE) -ENDIF(WIN32) - LIST(APPEND external_project_dependencies zlib) From 642dc356aef25b13c08b5fc2d64c9bbe600f707d Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 00:25:46 +0800 Subject: [PATCH 096/119] clean travis ci --- .travis.yml | 9 ++------- paddle/scripts/travis/before_install.osx.sh | 4 +--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index b49d4638d7..61d31132b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,15 +24,10 @@ addons: - wget - git - build-essential - - python + - libatlas-base-dev - python-pip - - python2.7-dev - - python-numpy - - python-wheel - curl - - lcov - graphviz - - swig - clang-format-3.8 - automake - libtool @@ -50,7 +45,7 @@ before_install: fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then paddle/scripts/travis/before_install.osx.sh; fi - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi - - pip install wheel protobuf sphinx recommonmark virtualenv numpy sphinx_rtd_theme pre-commit requests==2.9.2 LinkChecker + - pip install protobuf sphinx recommonmark sphinx_rtd_theme virtualenv pre-commit requests==2.9.2 LinkChecker script: - paddle/scripts/travis/main.sh notifications: diff --git a/paddle/scripts/travis/before_install.osx.sh b/paddle/scripts/travis/before_install.osx.sh index 89742d67f5..fd113d313e 100755 --- a/paddle/scripts/travis/before_install.osx.sh +++ b/paddle/scripts/travis/before_install.osx.sh @@ -1,6 +1,4 @@ #!/bin/bash brew update brew tap homebrew/science -brew install python -sudo pip install --upgrade protobuf -brew install cmake python wget md5sha1sum +brew install openblas md5sha1sum From 572d8254ea63a88b9c382ba2e91e53e90994e620 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 00:25:58 +0800 Subject: [PATCH 097/119] Clean cmake --- CMakeLists.txt | 26 +++++++++++----- cmake/cblas.cmake | 24 ++++++++------- cmake/check_packages.cmake | 14 --------- cmake/cpplint.cmake | 2 +- cmake/definitions.cmake | 62 -------------------------------------- cmake/util.cmake | 4 +-- paddle/cuda/CMakeLists.txt | 2 +- 7 files changed, 36 insertions(+), 98 deletions(-) delete mode 100644 cmake/check_packages.cmake delete mode 100644 cmake/definitions.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 784876f089..9ed757bd1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,18 @@ -cmake_minimum_required(VERSION 2.8) +# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +cmake_minimum_required(VERSION 3.0) project(paddle CXX C) @@ -6,11 +20,11 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(PROJ_ROOT ${CMAKE_SOURCE_DIR}) find_package(Sphinx) -find_package(Doxygen) find_package(CUDA QUIET) find_package(Git REQUIRED) find_package(Threads REQUIRED) +include(system) include(simd) ###################### Configurations ############################ @@ -35,9 +49,8 @@ include(external/gflags) # download, build, install gflags include(external/glog) # download, build, install glog include(external/gtest) # download, build, install gtest include(external/protobuf) # download, build, install protobuf -include(external/openblas) # download, build, install openblas include(external/python) # download, build, install python -include(external/numpy) # download, build, install numpy +include(external/openblas) # download, build, install openblas include(external/swig) # download, build, install swig include(external/warpctc) # download, build, install warpctc @@ -52,8 +65,7 @@ include(version) # set PADDLE_VERSION include(coveralls) # set code coverage include(python_module) # set python module -include(check_packages) # check configuration -include(definitions) # add paddle definitions +include(configure) # add paddle env configuration include_directories("${PROJ_ROOT}") include_directories("${PROJ_ROOT}/paddle/cuda/include") @@ -64,7 +76,7 @@ set(EXTERNAL_LIBS ${GFLAGS_LIBRARIES} ${GLOG_LIBRARIES} ${CBLAS_LIBRARIES} - ${PROTOBUF_LIBRARIES} + ${PROTOBUF_LIBRARY} ${ZLIB_LIBRARIES} ) diff --git a/cmake/cblas.cmake b/cmake/cblas.cmake index 685334c658..4e1ae7dc81 100644 --- a/cmake/cblas.cmake +++ b/cmake/cblas.cmake @@ -13,6 +13,7 @@ # system paths. # +set(CBLAS_FOUND OFF) ## Find MKL First. set(MKL_ROOT $ENV{MKL_ROOT} CACHE PATH "Folder contains MKL") @@ -35,11 +36,12 @@ find_library(MKL_INTEL_LP64 NAMES mkl_intel_lp64 PATHS if(MKL_INCLUDE_DIR AND MKL_CORE_LIB AND MKL_SEQUENTIAL_LIB AND MKL_INTEL_LP64) set(CBLAS_PROVIDER MKL) set(CBLAS_INC_DIR ${MKL_INCLUDE_DIR}) - set(CBLAS_LIBS ${MKL_INTEL_LP64} + set(CBLAS_LIBRARIES ${MKL_INTEL_LP64} ${MKL_SEQUENTIAL_LIB} ${MKL_CORE_LIB}) add_definitions(-DPADDLE_USE_MKL) - message(STATUS "Found MKL (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBS})") + message(STATUS "Found MKL (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") + set(CBLAS_FOUND ON) return() # return file. endif() @@ -68,9 +70,10 @@ find_library(ATLAS_LIB NAMES lapack_atlas liblapack_atlas.so.3 if(ATLAS_INC_DIR AND ATLAS_CBLAS_LIB AND ATLAS_LIB) set(CBLAS_PROVIDER ATLAS) set(CBLAS_INC_DIR ${ATLAS_INC_DIR} ${ATLAS_CLAPACK_INC_DIR}) - set(CBLAS_LIBS ${ATLAS_LIB} ${ATLAS_CBLAS_LIB}) + set(CBLAS_LIBRARIES ${ATLAS_LIB} ${ATLAS_CBLAS_LIB}) add_definitions(-DPADDLE_USE_ATLAS) - message(STATUS "Found Atlas (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBS})") + message(STATUS "Found Atlas (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") + set(CBLAS_FOUND ON) return() endif() @@ -98,8 +101,9 @@ find_library(OPENBLAS_LIB NAMES openblas if(OPENBLAS_INC_DIR AND OPENBLAS_LIB) set(CBLAS_PROVIDER OPENBLAS) set(CBLAS_INC_DIR ${OPENBLAS_INC_DIR}) - set(CBLAS_LIBS ${OPENBLAS_LIB}) - message(STATUS "Found OpenBlas (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBS})") + set(CBLAS_LIBRARIES ${OPENBLAS_LIB}) + message(STATUS "Found OpenBlas (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBRARIES})") + set(CBLAS_FOUND ON) return() endif() @@ -130,9 +134,7 @@ find_library(REFERENCE_CBLAS_LIBRARY NAMES cblas PATHS if (REFERENCE_CBLAS_INCLUDE_DIR AND REFERENCE_CBLAS_LIBRARY) set(CBLAS_PROVIDER REFERENCE) set(CBLAS_INC_DIR ${REFERENCE_CBLAS_INCLUDE_DIR}) - set(CBLAS_LIBS ${REFERENCE_CBLAS_LIBRARY}) - return() + set(CBLAS_LIBRARIES ${REFERENCE_CBLAS_LIBRARY}) + message(STATUS "Found reference-cblas (include: ${CBLAS_INC_DIR}, library: ${CBLAS_LIBS})") + set(CBLAS_FOUND ON) endif() - -message(FATAL_ERROR "CBlas must be set. Paddle support MKL, ATLAS, OpenBlas, reference-cblas." - " Try set MKL_ROOT, ATLAS_ROOT, OPENBLAS_ROOT or REFERENCE_CBLAS_ROOT.") diff --git a/cmake/check_packages.cmake b/cmake/check_packages.cmake deleted file mode 100644 index 8f0ed26256..0000000000 --- a/cmake/check_packages.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# Check package for each cmake option - -if(WITH_GPU) - find_package(CUDA REQUIRED) # CUDA is required when use gpu -endif(WITH_GPU) - -if(WITH_DOC) - find_package(Sphinx REQUIRED) - find_python_module(recommonmark REQUIRED) -endif(WITH_DOC) - -if(WITH_SWIG_PY) - find_python_module(wheel REQUIRED) # package wheel -endif(WITH_SWIG_PY) diff --git a/cmake/cpplint.cmake b/cmake/cpplint.cmake index 241af9a083..38c636b30e 100644 --- a/cmake/cpplint.cmake +++ b/cmake/cpplint.cmake @@ -53,7 +53,7 @@ macro(add_style_check_target TARGET_NAME) if(LINT MATCHES ON) add_custom_command(TARGET ${TARGET_NAME} PRE_BUILD - COMMAND "${PYTHON_EXECUTABLE}" "${PROJ_ROOT}/paddle/scripts/cpplint.py" + COMMAND env ${py_env} "${PYTHON_EXECUTABLE}" "${PROJ_ROOT}/paddle/scripts/cpplint.py" "--filter=${STYLE_FILTER}" ${filename} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) endif() diff --git a/cmake/definitions.cmake b/cmake/definitions.cmake deleted file mode 100644 index 99a52ad764..0000000000 --- a/cmake/definitions.cmake +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -if(WITH_DSO) - add_definitions(-DPADDLE_USE_DSO) -endif(WITH_DSO) - -if(WITH_DOUBLE) - add_definitions(-DPADDLE_TYPE_DOUBLE) -endif(WITH_DOUBLE) - -if(NOT WITH_TIMER) - add_definitions(-DPADDLE_DISABLE_TIMER) -endif(NOT WITH_TIMER) - -if(NOT WITH_PROFILER) - add_definitions(-DPADDLE_DISABLE_PROFILER) -endif(NOT WITH_PROFILER) - -if(NOT WITH_GPU) - add_definitions(-DPADDLE_ONLY_CPU) - add_definitions(-DHPPL_STUB_FUNC) - - list(APPEND CMAKE_CXX_SOURCE_FILE_EXTENSIONS cu) -else() - if(${CUDA_VERSION_MAJOR} VERSION_LESS 7) - message(FATAL_ERROR "Paddle need CUDA >= 7.0 to compile") - endif() - - if(NOT CUDNN_FOUND) - message(FATAL_ERROR "Paddle need cudnn to compile") - endif() - - if(WITH_AVX) - set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} "-Xcompiler ${AVX_FLAG}") - else(WITH_AVX) - set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} "-Xcompiler ${SSE3_FLAG}") - endif(WITH_AVX) - - # Include cuda and cudnn - include_directories(${CUDNN_INCLUDE_DIR}) - include_directories(${CUDA_TOOLKIT_INCLUDE}) -endif(NOT WITH_GPU) - -if(WITH_AVX) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${AVX_FLAG}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${AVX_FLAG}") -else(WITH_AVX) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SSE3_FLAG}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SSE3_FLAG}") -endif(WITH_AVX) diff --git a/cmake/util.cmake b/cmake/util.cmake index b8d20266f4..c9b48e5f8f 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -24,7 +24,7 @@ function(target_circle_link_libraries TARGET_NAME) list(APPEND libsInArgn ${arg}) endif() endforeach() - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") list(APPEND LIBS "-undefined dynamic_lookup") endif() list(REVERSE libsInArgn) @@ -105,7 +105,7 @@ function(link_paddle_exe TARGET_NAME) if(WITH_PYTHON) target_link_libraries(${TARGET_NAME} - ${PYTHON_LIBRARIES}) + ${PYTHON_LIBRARIES} util) endif() if(WITH_GPU) diff --git a/paddle/cuda/CMakeLists.txt b/paddle/cuda/CMakeLists.txt index 0a05897854..57fb89608f 100755 --- a/paddle/cuda/CMakeLists.txt +++ b/paddle/cuda/CMakeLists.txt @@ -88,7 +88,7 @@ else() ${CUDA_CXX_SOURCES}) endif() -add_dependencies(paddle_cuda warpctc) +add_dependencies(paddle_cuda ${external_project_dependencies}) add_style_check_target(paddle_cuda ${CUDA_SOURCES} From fc47492f4189ead6be33b6512a46663dc2541b69 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 01:06:16 +0800 Subject: [PATCH 098/119] Fix merge conflict bug and glog --- paddle/function/CMakeLists.txt | 2 - paddle/function/ContextProjectionOp.cpp | 52 ++++++++++----------- paddle/function/Function.cpp | 12 +++-- paddle/gserver/layers/ContextProjection.cpp | 6 ++- warp-ctc | 1 - 5 files changed, 38 insertions(+), 35 deletions(-) delete mode 160000 warp-ctc diff --git a/paddle/function/CMakeLists.txt b/paddle/function/CMakeLists.txt index cfa45e117c..b133d2419a 100644 --- a/paddle/function/CMakeLists.txt +++ b/paddle/function/CMakeLists.txt @@ -12,8 +12,6 @@ endif() add_library(paddle_function STATIC ${cpp_files} ${cu_objs}) add_dependencies(paddle_function ${external_project_dependencies}) -add_library(paddle_test_main STATIC TestMain.cpp) -add_dependencies(paddle_test_main ${external_project_dependencies}) if(WITH_GPU) if(WITH_TESTING) diff --git a/paddle/function/ContextProjectionOp.cpp b/paddle/function/ContextProjectionOp.cpp index bd367a859e..07907fc1ba 100644 --- a/paddle/function/ContextProjectionOp.cpp +++ b/paddle/function/ContextProjectionOp.cpp @@ -85,15 +85,15 @@ public: void calc(const Arguments& inputs, const Arguments& outputs, const Arguments& inouts) override { - CHECK_EQ(3, inputs.size()); - CHECK_EQ(1, outputs.size()); - CHECK_EQ(0, inouts.size()); + CHECK_EQ(3, static_cast(inputs.size())); + CHECK_EQ(1, static_cast(outputs.size())); + CHECK_EQ(0, static_cast(inouts.size())); CHECK(outputs[0].getData() && inputs[0].getData() && inputs[2].getData()); - CHECK_EQ(outputs[0].dims_.size(), 2); - CHECK_EQ(inputs[0].dims_.size(), 2); - CHECK_EQ(inputs[1].dims_.size(), 2); - CHECK_EQ(inputs[2].dims_.size(), 1); + CHECK_EQ(static_cast(outputs[0].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[0].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[1].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[2].dims_.size()), 1); /// dim of output = dim of input * context_length CHECK_EQ(outputs[0].dims_[1], inputs[0].dims_[1] * context_length_); /// dim of input == dim of weight @@ -202,15 +202,15 @@ public: void calc(const Arguments& inputs, const Arguments& outputs, const Arguments& inouts) override { - CHECK_EQ(3, inputs.size()); - CHECK_EQ(1, outputs.size()); - CHECK_EQ(0, inouts.size()); + CHECK_EQ(3, static_cast(inputs.size())); + CHECK_EQ(1, static_cast(outputs.size())); + CHECK_EQ(0, static_cast(inouts.size())); CHECK(outputs[0].getData() && inputs[2].getData()); - CHECK_EQ(outputs[0].dims_.size(), 2); - CHECK_EQ(inputs[0].dims_.size(), 2); - CHECK_EQ(inputs[1].dims_.size(), 2); - CHECK_EQ(inputs[2].dims_.size(), 1); + CHECK_EQ(static_cast(outputs[0].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[0].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[1].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[2].dims_.size()), 1); /// dim of input == dim of weight CHECK_EQ(inputs[0].dims_[1], inputs[1].dims_[1]); @@ -269,13 +269,13 @@ public: void calc(const Arguments& inputs, const Arguments& outputs, const Arguments& inouts) override { - CHECK_EQ(2, inputs.size()); - CHECK_EQ(1, outputs.size()); - CHECK_EQ(0, inouts.size()); + CHECK_EQ(2, static_cast(inputs.size())); + CHECK_EQ(1, static_cast(outputs.size())); + CHECK_EQ(0, static_cast(inouts.size())); CHECK(inputs[0].getData() && outputs[0].getData() && inputs[1].getData()); - CHECK_EQ(outputs[0].dims_.size(), 2); - CHECK_EQ(inputs[0].dims_.size(), 2); - CHECK_EQ(inputs[1].dims_.size(), 1); + CHECK_EQ(static_cast(outputs[0].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[0].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[1].dims_.size()), 1); CHECK_EQ(outputs[0].dims_[1], inputs[0].dims_[1] * context_length_); /// input and output has the same batch_size CHECK_EQ(inputs[0].dims_[0], outputs[0].dims_[0]); @@ -317,14 +317,14 @@ public: void calc(const Arguments& inputs, const Arguments& outputs, const Arguments& inouts) override { - CHECK_EQ(2, inputs.size()); - CHECK_EQ(1, outputs.size()); - CHECK_EQ(0, inouts.size()); + CHECK_EQ(2, static_cast(inputs.size())); + CHECK_EQ(1, static_cast(outputs.size())); + CHECK_EQ(0, static_cast(inouts.size())); CHECK(inputs[0].getData() && outputs[0].getData() && inputs[1].getData()); - CHECK_EQ(outputs[0].dims_.size(), 2); - CHECK_EQ(inputs[0].dims_.size(), 2); - CHECK_EQ(inputs[1].dims_.size(), 1); + CHECK_EQ(static_cast(outputs[0].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[0].dims_.size()), 2); + CHECK_EQ(static_cast(inputs[1].dims_.size()), 1); CHECK_EQ(outputs[0].dims_[1], inputs[0].dims_[1] * context_length_); auto out_grad_mat = std::make_shared::type>( diff --git a/paddle/function/Function.cpp b/paddle/function/Function.cpp index 6f82a8d053..614e76b8ac 100644 --- a/paddle/function/Function.cpp +++ b/paddle/function/Function.cpp @@ -46,28 +46,32 @@ bool FuncConfig::get(const std::string& key) const { template <> FuncConfig& FuncConfig::set(const std::string& key, size_t v) { - CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; + CHECK_EQ(static_cast(valueMap_.count(key)), 0) << "Duplicated value: " + << key; valueMap_[key].s = v; return *this; } template <> FuncConfig& FuncConfig::set(const std::string& key, real v) { - CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; + CHECK_EQ(static_cast(valueMap_.count(key)), 0) << "Duplicated value: " + << key; valueMap_[key].r = v; return *this; } template <> FuncConfig& FuncConfig::set(const std::string& key, int v) { - CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; + CHECK_EQ(static_cast(valueMap_.count(key)), 0) << "Duplicated value: " + << key; valueMap_[key].i = v; return *this; } template <> FuncConfig& FuncConfig::set(const std::string& key, bool v) { - CHECK_EQ(valueMap_.count(key), 0) << "Duplicated value: " << key; + CHECK_EQ(static_cast(valueMap_.count(key)), 0) << "Duplicated value: " + << key; valueMap_[key].b = v; return *this; } diff --git a/paddle/gserver/layers/ContextProjection.cpp b/paddle/gserver/layers/ContextProjection.cpp index e947b2b9ec..ee4db21989 100644 --- a/paddle/gserver/layers/ContextProjection.cpp +++ b/paddle/gserver/layers/ContextProjection.cpp @@ -111,7 +111,8 @@ void ContextProjection::forward() { size_t dim = out_->value->getWidth(); CHECK_EQ(dim, input_dim * config_.context_length()); size_t batch_size = in_->value->getHeight(); - CHECK_EQ(forward_.size(), 1) << "Only one forward function here"; + CHECK_EQ(static_cast(forward_.size()), 1) + << "Only one forward function here"; REGISTER_TIMER_INFO("ContextProjectionForward", getName().c_str()); bool is_padding = config_.trainable_padding(); @@ -154,7 +155,8 @@ void ContextProjection::backward(const UpdateCallback& callback) { CHECK_EQ(dim, input_dim * config_.context_length()); size_t batch_size = in_->value->getHeight(); CHECK_EQ(batch_size, out_->value->getHeight()); - CHECK_EQ(backward_.size(), 1) << "Only one backward function here"; + CHECK_EQ(static_cast(backward_.size()), 1) + << "Only one backward function here"; REGISTER_TIMER_INFO("ContextProjectionBackward", getName().c_str()); bool is_padding = config_.trainable_padding(); diff --git a/warp-ctc b/warp-ctc deleted file mode 160000 index bd535c8d44..0000000000 --- a/warp-ctc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd535c8d44e03c8ebd2d768e06c8c05fdccd11d2 From 3ecc63ad165913b4b62f41a009c9e1c2406a3441 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 11:08:22 +0800 Subject: [PATCH 099/119] Add python in travis ci for paddle version command --- .travis.yml | 2 ++ paddle/api/CMakeLists.txt | 24 ++++++++++++------------ paddle/api/test/CMakeLists.txt | 2 +- paddle/api/test/run_tests.sh | 4 ---- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 61d31132b7..27a4be38f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,9 @@ addons: - git - build-essential - libatlas-base-dev + - python - python-pip + - python2.7-dev - curl - graphviz - clang-format-3.8 diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index dd617e3268..3ac50e34bb 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -94,17 +94,17 @@ add_dependencies(python_api_wheel python_swig_sources paddle_cuda) if(WITH_TESTING) - SET(PIP_SOURCES_DIR ${PYTHON_SOURCES_DIR}/pip) - ExternalProject_Add(pip - ${EXTERNAL_PROJECT_LOG_ARGS} - GIT_REPOSITORY https://github.com/pypa/pip.git - GIT_TAG 9.0.1 - PREFIX ${PIP_SOURCES_DIR} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - BUILD_IN_SOURCE 1 - DEPENDS python setuptools python_api_wheel - ) + SET(PIP_SOURCES_DIR ${PYTHON_SOURCES_DIR}/pip) + ExternalProject_Add(pip + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY https://github.com/pypa/pip.git + GIT_TAG 9.0.1 + PREFIX ${PIP_SOURCES_DIR} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools python_api_wheel + ) add_subdirectory(test) endif() diff --git a/paddle/api/test/CMakeLists.txt b/paddle/api/test/CMakeLists.txt index 985df6f56e..a2fa623c80 100644 --- a/paddle/api/test/CMakeLists.txt +++ b/paddle/api/test/CMakeLists.txt @@ -1,2 +1,2 @@ add_test(NAME test_swig_api - COMMAND bash ${PROJ_ROOT}/paddle/api/test/run_tests.sh ${PYTHON_EXECUTABLE} ${PYTHON_INSTALL_DIR}/bin/pip) + COMMAND bash ${PROJ_ROOT}/paddle/api/test/run_tests.sh ${PYTHON_EXECUTABLE}) diff --git a/paddle/api/test/run_tests.sh b/paddle/api/test/run_tests.sh index f00ec2c967..bcf06afa86 100755 --- a/paddle/api/test/run_tests.sh +++ b/paddle/api/test/run_tests.sh @@ -20,10 +20,6 @@ popd > /dev/null cd $SCRIPTPATH -# rm -rf .test_env -# virtualenv .test_env -# source .test_env/bin/activate - $1 -m pip install ../../dist/*.whl test_list="testArguments.py testGradientMachine.py testMatrix.py testVector.py testTrain.py testTrainer.py" From 0407902592d61addceeb5b82313e63225911e6df Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 11:59:09 +0800 Subject: [PATCH 100/119] Add wheel for paddle version command --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 27a4be38f5..4c65b8c7e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ before_install: fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then paddle/scripts/travis/before_install.osx.sh; fi - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi - - pip install protobuf sphinx recommonmark sphinx_rtd_theme virtualenv pre-commit requests==2.9.2 LinkChecker + - pip install wheel protobuf sphinx recommonmark sphinx_rtd_theme virtualenv pre-commit requests==2.9.2 LinkChecker script: - paddle/scripts/travis/main.sh notifications: From 7b9c9696fc007115dd6c8dfba843fa9220a98937 Mon Sep 17 00:00:00 2001 From: Helin Wang Date: Wed, 4 Jan 2017 21:00:32 -0800 Subject: [PATCH 101/119] =?UTF-8?q?remove=20double=20spaces,=20remove=20ch?= =?UTF-8?q?inese=20character=20"=EF=BC=8C"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/howto/usage/k8s/k8s_aws_en.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/howto/usage/k8s/k8s_aws_en.md b/doc/howto/usage/k8s/k8s_aws_en.md index 422dc3bd81..b04bfba590 100644 --- a/doc/howto/usage/k8s/k8s_aws_en.md +++ b/doc/howto/usage/k8s/k8s_aws_en.md @@ -4,10 +4,10 @@ To use AWS, we need to sign up an AWS account on Amazon's Web site. An AWS account allows us to login to the AWS Console Web interface to -create IAM users and user groups. Usually, we create a user group with +create IAM users and user groups. Usually, we create a user group with privileges required to run PaddlePaddle, and we create users for those who are going to run PaddlePaddle and add these users into the -group. IAM users can identify themselves using password and tokens, +group. IAM users can identify themselves using password and tokens, where passwords allows users to log in to the AWS Console, and tokens make it easy for users to submit and inspect jobs from the command line. @@ -360,7 +360,7 @@ In one time of distributed training, user will confirm the PaddlePaddle node num ####Create PaddlePaddle Node -After Kubernetes master gets the request, it will parse the yaml file and create several pods (defined by PaddlePaddle's node number), Kubernetes will allocate these pods onto cluster's node. A pod represents a PaddlePaddle node, when pod is successfully allocated onto one physical/virtual machine, Kubernetes will startup the container in the pod, and this container will use the environment variables in yaml file and start up `paddle pserver` and `paddle trainer` processes. +After Kubernetes master gets the request, it will parse the yaml file and create several pods (defined by PaddlePaddle's node number), Kubernetes will allocate these pods onto cluster's node. A pod represents a PaddlePaddle node, when pod is successfully allocated onto one physical/virtual machine, Kubernetes will startup the container in the pod, and this container will use the environment variables in yaml file and start up `paddle pserver` and `paddle trainer` processes. ####Start up Training @@ -661,6 +661,6 @@ Sometimes we might need to create or manage the cluster on AWS manually with lim ### Some Presumptions * Instances run on CoreOS, the official IAM. -* Kubernetes node use instance storage, no EBS get mounted. Etcd is running on additional node. +* Kubernetes node use instance storage, no EBS get mounted. Etcd is running on additional node. * For networking, we use Flannel network at this moment, we will use Calico solution later on. * When you create a service with Type=LoadBalancer, Kubernetes will create and ELB, and create a security group for the ELB. From 19dc8df41d69396c9810402f830cbc01dd49f19e Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 14:47:21 +0800 Subject: [PATCH 102/119] Update travis ci pip --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4c65b8c7e9..ba2f8482b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,7 @@ before_install: fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then paddle/scripts/travis/before_install.osx.sh; fi - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi + - pip install --upgrade pip - pip install wheel protobuf sphinx recommonmark sphinx_rtd_theme virtualenv pre-commit requests==2.9.2 LinkChecker script: - paddle/scripts/travis/main.sh From be8b12684508396987a972eb1188e38a9253b10f Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 16:56:49 +0800 Subject: [PATCH 103/119] Move Execepts into arch/osx dir --- .gitignore | 1 + paddle/api/PaddleAPI.h | 2 +- paddle/api/Util.cpp | 3 +-- paddle/gserver/dataproviders/DataProvider.h | 2 +- paddle/gserver/dataproviders/PyDataProvider.cpp | 3 +-- paddle/gserver/layers/GruCompute.h | 2 +- paddle/gserver/layers/LstmCompute.h | 2 +- paddle/gserver/layers/MultinomialSampler.h | 2 +- paddle/math/BaseMatrix.h | 2 +- paddle/math/Matrix.h | 2 +- paddle/math/TensorExpression.h | 2 +- paddle/math/Vector.h | 2 +- paddle/math/tests/test_FPException.cpp | 4 ++-- paddle/parameter/ParallelParameter.h | 2 +- paddle/parameter/Parameter.h | 2 +- paddle/parameter/ParameterUpdateFunctions.h | 2 +- paddle/pserver/BaseClient.h | 2 +- paddle/pserver/ParameterClient2.h | 2 +- paddle/pserver/ParameterServer2.h | 2 +- paddle/trainer/Trainer.cpp | 3 +-- paddle/trainer/TrainerMain.cpp | 3 +-- paddle/utils/CpuId.h | 2 +- paddle/utils/Excepts.h | 2 ++ paddle/utils/Locks.h | 2 +- paddle/utils/Util.h | 2 +- paddle/utils/Version.h | 2 +- paddle/utils/{ => arch/osx}/Excepts.cpp | 4 +--- paddle/utils/common.h | 2 ++ 28 files changed, 31 insertions(+), 32 deletions(-) rename paddle/utils/{ => arch/osx}/Excepts.cpp (97%) diff --git a/.gitignore b/.gitignore index 1c9730a5ad..f963c2660d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ Makefile *~ bazel-* +third_party/ diff --git a/paddle/api/PaddleAPI.h b/paddle/api/PaddleAPI.h index 81c9eed0bc..364d19f941 100644 --- a/paddle/api/PaddleAPI.h +++ b/paddle/api/PaddleAPI.h @@ -19,8 +19,8 @@ limitations under the License. */ #include #include #include +#include "paddle/utils/Common.h" #include "paddle/utils/GlobalConstants.h" -#include "paddle/utils/common.h" /// Import PaddlePaddle's enumeration into global namespace. using namespace paddle::enumeration_wrapper; // NOLINT diff --git a/paddle/api/Util.cpp b/paddle/api/Util.cpp index c3f739568f..54d67aa62f 100644 --- a/paddle/api/Util.cpp +++ b/paddle/api/Util.cpp @@ -15,12 +15,11 @@ limitations under the License. */ #include "PaddleAPI.h" #include "paddle/parameter/Parameter.h" -#include "paddle/utils/Excepts.h" +#include "paddle/utils/Common.h" #include "paddle/utils/Flags.h" #include "paddle/utils/PythonUtil.h" #include "paddle/utils/Util.h" -#include #include #include #include diff --git a/paddle/gserver/dataproviders/DataProvider.h b/paddle/gserver/dataproviders/DataProvider.h index 5f031fc7c0..9a2ad7567f 100644 --- a/paddle/gserver/dataproviders/DataProvider.h +++ b/paddle/gserver/dataproviders/DataProvider.h @@ -30,12 +30,12 @@ limitations under the License. */ #include "paddle/math/Vector.h" #include "paddle/parameter/Argument.h" #include "paddle/utils/ClassRegistrar.h" +#include "paddle/utils/Common.h" #include "paddle/utils/Locks.h" #include "paddle/utils/Logging.h" #include "paddle/utils/Queue.h" #include "paddle/utils/ThreadLocal.h" #include "paddle/utils/Util.h" -#include "paddle/utils/common.h" namespace paddle { /** diff --git a/paddle/gserver/dataproviders/PyDataProvider.cpp b/paddle/gserver/dataproviders/PyDataProvider.cpp index 5bdd55309c..b53790e764 100644 --- a/paddle/gserver/dataproviders/PyDataProvider.cpp +++ b/paddle/gserver/dataproviders/PyDataProvider.cpp @@ -13,8 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #include "PyDataProvider.h" -#include -#include "paddle/utils/Excepts.h" +#include "paddle/utils/Common.h" #include "paddle/utils/PythonUtil.h" #include "paddle/utils/Util.h" diff --git a/paddle/gserver/layers/GruCompute.h b/paddle/gserver/layers/GruCompute.h index a56af21317..3340e38e62 100644 --- a/paddle/gserver/layers/GruCompute.h +++ b/paddle/gserver/layers/GruCompute.h @@ -16,7 +16,7 @@ limitations under the License. */ #include "ModelConfig.pb.h" #include "hl_gpu.h" -#include "paddle/utils/common.h" +#include "paddle/utils/Common.h" namespace paddle { diff --git a/paddle/gserver/layers/LstmCompute.h b/paddle/gserver/layers/LstmCompute.h index 0d65b4158e..2588fad279 100644 --- a/paddle/gserver/layers/LstmCompute.h +++ b/paddle/gserver/layers/LstmCompute.h @@ -16,7 +16,7 @@ limitations under the License. */ #include "ModelConfig.pb.h" #include "hl_gpu.h" -#include "paddle/utils/common.h" +#include "paddle/utils/Common.h" namespace paddle { diff --git a/paddle/gserver/layers/MultinomialSampler.h b/paddle/gserver/layers/MultinomialSampler.h index b48073c80b..546ef9c1f2 100644 --- a/paddle/gserver/layers/MultinomialSampler.h +++ b/paddle/gserver/layers/MultinomialSampler.h @@ -16,7 +16,7 @@ limitations under the License. */ #include #include -#include "paddle/utils/common.h" +#include "paddle/utils/Common.h" namespace paddle { diff --git a/paddle/math/BaseMatrix.h b/paddle/math/BaseMatrix.h index 8f9bc9e823..8691c87ac3 100644 --- a/paddle/math/BaseMatrix.h +++ b/paddle/math/BaseMatrix.h @@ -16,7 +16,7 @@ limitations under the License. */ #include #include #include "TensorExpression.h" -#include "paddle/utils/common.h" +#include "paddle/utils/Common.h" namespace paddle { diff --git a/paddle/math/Matrix.h b/paddle/math/Matrix.h index 4865a081a5..ceac0212d2 100644 --- a/paddle/math/Matrix.h +++ b/paddle/math/Matrix.h @@ -26,8 +26,8 @@ limitations under the License. */ #include "BaseMatrix.h" #include "MemoryHandle.h" #include "Vector.h" +#include "paddle/utils/Common.h" #include "paddle/utils/ThreadLocal.h" -#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/math/TensorExpression.h b/paddle/math/TensorExpression.h index f3d60e4003..6fd60e7f3c 100644 --- a/paddle/math/TensorExpression.h +++ b/paddle/math/TensorExpression.h @@ -16,8 +16,8 @@ limitations under the License. */ #include #include #include "hl_tensor_ops.h" +#include "paddle/utils/Common.h" #include "paddle/utils/Logging.h" -#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/math/Vector.h b/paddle/math/Vector.h index b4347a70f8..9af6e30c9e 100644 --- a/paddle/math/Vector.h +++ b/paddle/math/Vector.h @@ -21,8 +21,8 @@ limitations under the License. */ #include "BaseMatrix.h" #include "MemoryHandle.h" +#include "paddle/utils/Common.h" #include "paddle/utils/Thread.h" -#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/math/tests/test_FPException.cpp b/paddle/math/tests/test_FPException.cpp index 6aa5891bce..3836f7fc0f 100644 --- a/paddle/math/tests/test_FPException.cpp +++ b/paddle/math/tests/test_FPException.cpp @@ -28,10 +28,10 @@ limitations under the License. */ * so we can add some tricks to prevent exp calculate an excessive value. * */ -#include + #include #include "paddle/math/Matrix.h" -#include "paddle/utils/Excepts.h" +#include "paddle/utils/Common.h" using namespace paddle; // NOLINT diff --git a/paddle/parameter/ParallelParameter.h b/paddle/parameter/ParallelParameter.h index 1ee220d2dc..2e7c18b808 100644 --- a/paddle/parameter/ParallelParameter.h +++ b/paddle/parameter/ParallelParameter.h @@ -26,9 +26,9 @@ limitations under the License. */ #include "paddle/math/Vector.h" #include "paddle/parameter/Parameter.h" #include "paddle/parameter/ParameterUpdateFunctions.h" +#include "paddle/utils/Common.h" #include "paddle/utils/Flags.h" #include "paddle/utils/Locks.h" -#include "paddle/utils/common.h" #include "ParameterConfig.pb.h" diff --git a/paddle/parameter/Parameter.h b/paddle/parameter/Parameter.h index e05137b315..72c8336799 100644 --- a/paddle/parameter/Parameter.h +++ b/paddle/parameter/Parameter.h @@ -26,11 +26,11 @@ limitations under the License. */ #include "ParameterUpdaterHook.h" #include "paddle/math/Matrix.h" #include "paddle/math/Vector.h" +#include "paddle/utils/Common.h" #include "paddle/utils/GlobalConstants.h" #include "paddle/utils/Locks.h" #include "paddle/utils/ThreadLocal.h" #include "paddle/utils/Util.h" -#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/parameter/ParameterUpdateFunctions.h b/paddle/parameter/ParameterUpdateFunctions.h index 2cb3798717..0fca280149 100644 --- a/paddle/parameter/ParameterUpdateFunctions.h +++ b/paddle/parameter/ParameterUpdateFunctions.h @@ -15,7 +15,7 @@ limitations under the License. */ #pragma once #include "paddle/math/Vector.h" -#include "paddle/utils/common.h" +#include "paddle/utils/Common.h" namespace paddle { diff --git a/paddle/pserver/BaseClient.h b/paddle/pserver/BaseClient.h index ccf05ae1ca..11d7a147bf 100644 --- a/paddle/pserver/BaseClient.h +++ b/paddle/pserver/BaseClient.h @@ -17,8 +17,8 @@ limitations under the License. */ #include "ParameterService.pb.h" #include "paddle/math/Matrix.h" #include "paddle/pserver/ProtoServer.h" +#include "paddle/utils/Common.h" #include "paddle/utils/Queue.h" -#include "paddle/utils/common.h" namespace paddle { diff --git a/paddle/pserver/ParameterClient2.h b/paddle/pserver/ParameterClient2.h index 70cfc6d700..89b3ddd502 100644 --- a/paddle/pserver/ParameterClient2.h +++ b/paddle/pserver/ParameterClient2.h @@ -23,11 +23,11 @@ limitations under the License. */ #include "paddle/math/Vector.h" #include "paddle/parameter/Parameter.h" #include "paddle/pserver/BaseClient.h" +#include "paddle/utils/Common.h" #include "paddle/utils/Flags.h" #include "paddle/utils/Locks.h" #include "paddle/utils/Queue.h" #include "paddle/utils/Util.h" -#include "paddle/utils/common.h" #include "ParameterService.pb.h" diff --git a/paddle/pserver/ParameterServer2.h b/paddle/pserver/ParameterServer2.h index 79d1eb97ff..0f5a589590 100644 --- a/paddle/pserver/ParameterServer2.h +++ b/paddle/pserver/ParameterServer2.h @@ -29,10 +29,10 @@ limitations under the License. */ #include "paddle/math/Vector.h" #include "paddle/parameter/Parameter.h" #include "paddle/parameter/ParameterOptimizer.h" +#include "paddle/utils/Common.h" #include "paddle/utils/Locks.h" #include "paddle/utils/Stat.h" #include "paddle/utils/ThreadLocal.h" -#include "paddle/utils/common.h" #include "ParameterService.pb.h" diff --git a/paddle/trainer/Trainer.cpp b/paddle/trainer/Trainer.cpp index 09e0a213ab..8465addaf9 100644 --- a/paddle/trainer/Trainer.cpp +++ b/paddle/trainer/Trainer.cpp @@ -14,7 +14,6 @@ limitations under the License. */ #include "Trainer.h" -#include #include #include @@ -24,7 +23,7 @@ limitations under the License. */ #include -#include "paddle/utils/Excepts.h" +#include "paddle/utils/Common.h" #include "paddle/utils/GlobalConstants.h" #include "paddle/utils/PythonUtil.h" #include "paddle/utils/Stat.h" diff --git a/paddle/trainer/TrainerMain.cpp b/paddle/trainer/TrainerMain.cpp index 947f9cadcc..e2fbd21e14 100644 --- a/paddle/trainer/TrainerMain.cpp +++ b/paddle/trainer/TrainerMain.cpp @@ -12,9 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -#include #include "paddle/pserver/ParameterServer2.h" -#include "paddle/utils/Excepts.h" +#include "paddle/utils/Common.h" #include "paddle/utils/PythonUtil.h" #include "paddle/utils/StringUtil.h" diff --git a/paddle/utils/CpuId.h b/paddle/utils/CpuId.h index 1218e8194c..0f3985cc7b 100644 --- a/paddle/utils/CpuId.h +++ b/paddle/utils/CpuId.h @@ -11,7 +11,7 @@ limitations under the License. */ #pragma once -#include "common.h" +#include "Common.h" namespace paddle { diff --git a/paddle/utils/Excepts.h b/paddle/utils/Excepts.h index dc3369b7e8..5c2c504f53 100644 --- a/paddle/utils/Excepts.h +++ b/paddle/utils/Excepts.h @@ -15,6 +15,8 @@ limitations under the License. */ #ifndef EXCEPTS_H_ #define EXCEPTS_H_ +#include + #if defined(__APPLE__) || defined(__OSX__) int fegetexcept(void); diff --git a/paddle/utils/Locks.h b/paddle/utils/Locks.h index a21872e89e..e87abb9139 100644 --- a/paddle/utils/Locks.h +++ b/paddle/utils/Locks.h @@ -19,7 +19,7 @@ limitations under the License. */ #include #include -#include "common.h" +#include "Common.h" namespace paddle { diff --git a/paddle/utils/Util.h b/paddle/utils/Util.h index dc15ada586..613844669d 100644 --- a/paddle/utils/Util.h +++ b/paddle/utils/Util.h @@ -26,9 +26,9 @@ limitations under the License. */ #include #include +#include "Common.h" #include "Logging.h" #include "TrainerConfig.pb.h" -#include "common.h" #include "Flags.h" #include "hl_gpu.h" diff --git a/paddle/utils/Version.h b/paddle/utils/Version.h index aa5df32438..f53d6420bb 100644 --- a/paddle/utils/Version.h +++ b/paddle/utils/Version.h @@ -15,7 +15,7 @@ limitations under the License. */ #pragma once #include #include -#include "common.h" +#include "Common.h" namespace paddle { diff --git a/paddle/utils/Excepts.cpp b/paddle/utils/arch/osx/Excepts.cpp similarity index 97% rename from paddle/utils/Excepts.cpp rename to paddle/utils/arch/osx/Excepts.cpp index 4ddce35ed3..c8e904d8f9 100644 --- a/paddle/utils/Excepts.cpp +++ b/paddle/utils/arch/osx/Excepts.cpp @@ -12,12 +12,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -#include "Excepts.h" +#include "paddle/utils/Excepts.h" #if defined(__APPLE__) || defined(__OSX__) -#include - int fegetexcept(void) { static fenv_t fenv; return fegetenv(&fenv) ? -1 : (fenv.__control & FE_ALL_EXCEPT); diff --git a/paddle/utils/common.h b/paddle/utils/common.h index 202a9d980d..1f1d0255a5 100644 --- a/paddle/utils/common.h +++ b/paddle/utils/common.h @@ -14,6 +14,8 @@ limitations under the License. */ #pragma once +#include "Excepts.h" + /** * Disable copy macro. */ From 72b95533a120080908bec3fbc9f3c2607a4f6004 Mon Sep 17 00:00:00 2001 From: gangliao Date: Thu, 5 Jan 2017 17:17:18 +0800 Subject: [PATCH 104/119] Revise common to Common --- paddle/utils/{common.h => Common.h} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename paddle/utils/{common.h => Common.h} (100%) diff --git a/paddle/utils/common.h b/paddle/utils/Common.h similarity index 100% rename from paddle/utils/common.h rename to paddle/utils/Common.h From d0a5ce290a4f9ecdb984a465ed90a790e30d47a5 Mon Sep 17 00:00:00 2001 From: liaogang Date: Thu, 5 Jan 2017 18:06:00 +0800 Subject: [PATCH 105/119] Clean travis ci --- .travis.yml | 2 +- cmake/external/python.cmake | 35 ++++++------------------- paddle/scripts/travis/build_and_test.sh | 8 +++--- python/CMakeLists.txt | 2 +- 4 files changed, 15 insertions(+), 32 deletions(-) diff --git a/.travis.yml b/.travis.yml index ba2f8482b5..426f0eb746 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ before_install: fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then paddle/scripts/travis/before_install.osx.sh; fi - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi - - pip install --upgrade pip + - pip install --upgrade pip - pip install wheel protobuf sphinx recommonmark sphinx_rtd_theme virtualenv pre-commit requests==2.9.2 LinkChecker script: - paddle/scripts/travis/main.sh diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index 479ec301b7..e4c570479f 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -138,41 +138,22 @@ SET(NUMPY_SOURCES_DIR ${PYTHON_SOURCES_DIR}/numpy) SET(NUMPY_TAG_VERSION "v1.11.3") SET(NUMPY_VERSION "1.11.3") +SET(EGG_NAME "") +SET(PYTHON_NUMPY_INCLUDE_DIR "") IF(WIN32) SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}.egg") ELSE(WIN32) IF(APPLE) - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}-${MACOS_VERSION}-x86_64.egg") + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}-${MACOS_VERSION}") ELSE(APPLE) - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux-x86_64.egg") + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux") + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux") ENDIF(APPLE) -ENDIF(WIN32) -SET(PYTHON_NUMPY_INCLUDE_DIR "${PY_SITE_PACKAGES_PATH}/${EGG_NAME}/numpy/core/include") -IF(${PYTHON_FOUND}) # local python - SET(PYTHON_NUMPY_INCLUDE_DIR - "${PY_SITE_PACKAGES_PATH}/${EGG_NAME}/numpy/core/include") -ELSE(${PYTHON_FOUND}) # global python - SET(PYTHON_NUMPY_INCLUDE_DIR "") - SET(PY_SITE_PACKAGES_DIR "") - FILE(WRITE ${PROJECT_BINARY_DIR}/FindNumpyPath.py - "try: import site; print(site.getsitepackages())\nexcept:pass\n") - EXEC_PROGRAM("env ${py_env} ${PYTHON_EXECUTABLE}" ${PROJECT_BINARY_DIR} - ARGS "FindNumpyPath.py" OUTPUT_VARIABLE NUMPY_PATH) - - STRING(REPLACE "[" "" NUMPY_PATH "${NUMPY_PATH}") - STRING(REPLACE "]" "" NUMPY_PATH "${NUMPY_PATH}") - STRING(REPLACE "'" "" NUMPY_PATH "${NUMPY_PATH}") - STRING(REPLACE ", " ";" SITE_DIRS "${NUMPY_PATH}") - - FOREACH(SITE_DIR ${SITE_DIRS}) - IF(EXISTS ${SITE_DIR}) - LIST(APPEND PYTHON_NUMPY_INCLUDE_DIR - "${SITE_DIR}/${EGG_NAME}/numpy/core/include") - SET(PY_SITE_PACKAGES_DIR "${SITE_DIR}") - ENDIF() + FOREACH(suffix x86_64 intel fat64 fat32 universal) + LIST(APPEND PYTHON_NUMPY_INCLUDE_DIR ${PY_SITE_PACKAGES_PATH}/${EGG_NAME}-${suffix}.egg/numpy/core/include) ENDFOREACH() -ENDIF(${PYTHON_FOUND}) +ENDIF(WIN32) INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) diff --git a/paddle/scripts/travis/build_and_test.sh b/paddle/scripts/travis/build_and_test.sh index fb21712188..ffc48eae66 100755 --- a/paddle/scripts/travis/build_and_test.sh +++ b/paddle/scripts/travis/build_and_test.sh @@ -1,6 +1,8 @@ #!/bin/bash source ./common.sh +python -c 'import pip; print(pip.pep425tags.get_supported())' + if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then CMAKE_EXTRA="-DWITH_SWIG_PY=OFF" else @@ -14,11 +16,11 @@ if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then NRPOC=`nproc` make -j $NPROC make coveralls + sudo make install elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then NPROC=`sysctl -n hw.ncpu` make -j $NPROC env CTEST_OUTPUT_ON_FAILURE=1 make test ARGS="-j $NPROC" + sudo make install + sudo paddle version fi - -sudo make install -sudo paddle version diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 0a3599a47a..1cda4762eb 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -16,7 +16,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in add_custom_command(OUTPUT ${OUTPUT_DIR}/.timestamp COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_DIR}/.timestamp - DEPENDS gen_proto_py ${PY_FILES}) + DEPENDS gen_proto_py ${PY_FILES} ${external_project_dependencies}) add_custom_target(paddle_python ALL DEPENDS ${OUTPUT_DIR}/.timestamp) From b49745cd56eb882ad990c626c6c44846a8dbf247 Mon Sep 17 00:00:00 2001 From: liaogang Date: Fri, 6 Jan 2017 13:52:24 +0800 Subject: [PATCH 106/119] Add find system's swig --- .travis.yml | 1 + cmake/external/swig.cmake | 104 ++++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/.travis.yml b/.travis.yml index 426f0eb746..bc91855a85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ addons: - python-pip - python2.7-dev - curl + - swig - graphviz - clang-format-3.8 - automake diff --git a/cmake/external/swig.cmake b/cmake/external/swig.cmake index 5460b02c37..40088c65ef 100644 --- a/cmake/external/swig.cmake +++ b/cmake/external/swig.cmake @@ -12,59 +12,63 @@ # See the License for the specific language governing permissions and # limitations under the License. -# build swig as an external project -INCLUDE(ExternalProject) +FIND_PACKAGE(SWIG) -SET(SWIG_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/swig) -SET(SWIG_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/swig) -SET(SWIG_TARGET_VERSION "3.0.2") -SET(SWIG_DOWNLOAD_SRC_MD5 "62f9b0d010cef36a13a010dc530d0d41") -SET(SWIG_DOWNLOAD_WIN_MD5 "3f18de4fc09ab9abb0d3be37c11fbc8f") +IF(NOT SWIG_FOUND) + # build swig as an external project + INCLUDE(ExternalProject) -IF(WIN32) - # swig.exe available as pre-built binary on Windows: - ExternalProject_Add(swig - URL http://prdownloads.sourceforge.net/swig/swigwin-${SWIG_TARGET_VERSION}.zip - URL_MD5 ${SWIG_DOWNLOAD_WIN_MD5} - SOURCE_DIR ${SWIG_SOURCES_DIR} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - UPDATE_COMMAND "" - ) - SET(SWIG_DIR ${SWIG_SOURCES_DIR} CACHE FILEPATH "SWIG Directory" FORCE) - SET(SWIG_EXECUTABLE ${SWIG_SOURCES_DIR}/swig.exe CACHE FILEPATH "SWIG Executable" FORCE) -ELSE(WIN32) - # From PCRE configure - ExternalProject_Add(pcre - ${EXTERNAL_PROJECT_LOG_ARGS} - GIT_REPOSITORY https://github.com/svn2github/pcre.git - PREFIX ${SWIG_SOURCES_DIR}/pcre - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SWIG_INSTALL_DIR}/pcre - ) + SET(SWIG_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/swig) + SET(SWIG_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/swig) + SET(SWIG_TARGET_VERSION "3.0.2") + SET(SWIG_DOWNLOAD_SRC_MD5 "62f9b0d010cef36a13a010dc530d0d41") + SET(SWIG_DOWNLOAD_WIN_MD5 "3f18de4fc09ab9abb0d3be37c11fbc8f") - # swig uses bison find it by cmake and pass it down - FIND_PACKAGE(BISON) + IF(WIN32) + # swig.exe available as pre-built binary on Windows: + ExternalProject_Add(swig + URL http://prdownloads.sourceforge.net/swig/swigwin-${SWIG_TARGET_VERSION}.zip + URL_MD5 ${SWIG_DOWNLOAD_WIN_MD5} + SOURCE_DIR ${SWIG_SOURCES_DIR} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + UPDATE_COMMAND "" + ) + SET(SWIG_DIR ${SWIG_SOURCES_DIR} CACHE FILEPATH "SWIG Directory" FORCE) + SET(SWIG_EXECUTABLE ${SWIG_SOURCES_DIR}/swig.exe CACHE FILEPATH "SWIG Executable" FORCE) + ELSE(WIN32) + # From PCRE configure + ExternalProject_Add(pcre + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY https://github.com/svn2github/pcre.git + PREFIX ${SWIG_SOURCES_DIR}/pcre + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${SWIG_INSTALL_DIR}/pcre + ) - # From SWIG configure - ExternalProject_Add(swig - GIT_REPOSITORY https://github.com/swig/swig.git - GIT_TAG rel-3.0.10 - PREFIX ${SWIG_SOURCES_DIR} - CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && ./autogen.sh - CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && - env "PCRE_LIBS=${SWIG_INSTALL_DIR}/pcre/lib/libpcre.a ${SWIG_INSTALL_DIR}/pcre/lib/libpcrecpp.a ${SWIG_INSTALL_DIR}/pcre/lib/libpcreposix.a" - ./configure - --prefix=${SWIG_INSTALL_DIR} - --with-pcre-prefix=${SWIG_INSTALL_DIR}/pcre - BUILD_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make - INSTALL_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make install - UPDATE_COMMAND "" - DEPENDS pcre - ) + # swig uses bison find it by cmake and pass it down + FIND_PACKAGE(BISON) - SET(SWIG_DIR ${SWIG_INSTALL_DIR}/share/swig/${SWIG_TARGET_VERSION}) - SET(SWIG_EXECUTABLE ${SWIG_INSTALL_DIR}/bin/swig) -ENDIF(WIN32) + # From SWIG configure + ExternalProject_Add(swig + GIT_REPOSITORY https://github.com/swig/swig.git + GIT_TAG rel-3.0.10 + PREFIX ${SWIG_SOURCES_DIR} + CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && ./autogen.sh + CONFIGURE_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && + env "PCRE_LIBS=${SWIG_INSTALL_DIR}/pcre/lib/libpcre.a ${SWIG_INSTALL_DIR}/pcre/lib/libpcrecpp.a ${SWIG_INSTALL_DIR}/pcre/lib/libpcreposix.a" + ./configure + --prefix=${SWIG_INSTALL_DIR} + --with-pcre-prefix=${SWIG_INSTALL_DIR}/pcre + BUILD_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make + INSTALL_COMMAND cd ${SWIG_SOURCES_DIR}/src/swig && make install + UPDATE_COMMAND "" + DEPENDS pcre + ) -LIST(APPEND external_project_dependencies swig) + SET(SWIG_DIR ${SWIG_INSTALL_DIR}/share/swig/${SWIG_TARGET_VERSION}) + SET(SWIG_EXECUTABLE ${SWIG_INSTALL_DIR}/bin/swig) + ENDIF(WIN32) + + LIST(APPEND external_project_dependencies swig) +ENDIF(NOT SWIG_FOUND) From 411e234808ff9d0e23d2a65faf8a63cab71f3b52 Mon Sep 17 00:00:00 2001 From: chengxingyi Date: Fri, 6 Jan 2017 16:22:32 +0800 Subject: [PATCH 107/119] A traffic demo for ASC17 --- demo/traffic_prediction/README | 7 +++ demo/traffic_prediction/data/get_data.sh | 34 ++++++++++ demo/traffic_prediction/dataprovider.py | 77 +++++++++++++++++++++++ demo/traffic_prediction/gen_result.py | 47 ++++++++++++++ demo/traffic_prediction/predict.sh | 30 +++++++++ demo/traffic_prediction/train.sh | 30 +++++++++ demo/traffic_prediction/trainer_config.py | 43 +++++++++++++ 7 files changed, 268 insertions(+) create mode 100644 demo/traffic_prediction/README create mode 100755 demo/traffic_prediction/data/get_data.sh create mode 100644 demo/traffic_prediction/dataprovider.py create mode 100644 demo/traffic_prediction/gen_result.py create mode 100755 demo/traffic_prediction/predict.sh create mode 100755 demo/traffic_prediction/train.sh create mode 100755 demo/traffic_prediction/trainer_config.py diff --git a/demo/traffic_prediction/README b/demo/traffic_prediction/README new file mode 100644 index 0000000000..4c95188583 --- /dev/null +++ b/demo/traffic_prediction/README @@ -0,0 +1,7 @@ +run by: +cd ./data +sh get_data.sh +cd .. +sh train.sh +sh predict.sh + diff --git a/demo/traffic_prediction/data/get_data.sh b/demo/traffic_prediction/data/get_data.sh new file mode 100755 index 0000000000..52cf6608df --- /dev/null +++ b/demo/traffic_prediction/data/get_data.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e +set -x + +DIR="$( cd "$(dirname "$0")" ; pwd -P )" +cd $DIR + +#download the dataset +echo "Downloading traffic data..." +wget http://paddlepaddle.bj.bcebos.com/demo/traffic/traffic_data.tar.gz + +#extract package +echo "Unzipping..." +tar -zxvf traffic_data.tar.gz + +echo "data/speeds.csv" >> train.list +echo "data/speeds.csv" >> test.list +echo "data/speeds.csv" >> pred.list + +echo "Done." diff --git a/demo/traffic_prediction/dataprovider.py b/demo/traffic_prediction/dataprovider.py new file mode 100644 index 0000000000..bea0259b03 --- /dev/null +++ b/demo/traffic_prediction/dataprovider.py @@ -0,0 +1,77 @@ +# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle.trainer.PyDataProvider2 import * +import sys +import numpy as np +TERM_NUM = 24 +FORECASTING_NUM = 25 +LABEL_VALUE_NUM = 4 +def initHook(settings, file_list, **kwargs): + """ + Init hook is invoked before process data. It will set obj.slots and store data meta. + + :param settings: global object. It will passed to process routine. + :type obj: object + :param file_list: the meta file object, which passed from trainer_config.py,but unused in this function. + :param kwargs: unused other arguments. + """ + del kwargs #unused + + settings.pool_size = sys.maxint + #Use a time seires of the past as feature. + #Dense_vector's expression form is [float,float,...,float] + settings.slots = [dense_vector(TERM_NUM)] + #There are next FORECASTING_NUM fragments you need predict. + #Every predicted condition at time point has four states. + for i in range(FORECASTING_NUM): + settings.slots.append(integer_value(LABEL_VALUE_NUM)) + +@provider(init_hook=initHook, cache=CacheType.CACHE_PASS_IN_MEM, should_shuffle=True) +def process(settings, file_name): + with open(file_name) as f: + #abandon fields name + f.next() + for row_num, line in enumerate(f): + speeds = map(int,line.rstrip('\r\n').split(",")[1:]) + # Get the max index. + end_time = len(speeds) + # Scanning and generating samples + for i in range(TERM_NUM,end_time - FORECASTING_NUM): + # For dense slot + pre_spd = map(float,speeds[i-TERM_NUM:i]) + + # Integer value need predicting, values start from 0, so every one minus 1. + fol_spd = [i-1 for i in speeds[i:i + FORECASTING_NUM]] + + # Predicting label is missing, abandon the sample. + if -1 in fol_spd: + continue + yield [pre_spd] + fol_spd + +def predict_initHook(settings, file_list, **kwargs): + settings.pool_size = sys.maxint + settings.slots = [dense_vector(TERM_NUM)] + +@provider(init_hook=predict_initHook,should_shuffle=False) +def process_predict(settings, file_name): + with open(file_name) as f: + #abandon fields name + f.next() + for row_num, line in enumerate(f): + speeds = map(int,line.rstrip('\r\n').split(",")) + end_time = len(speeds) + pre_spd = map(float,speeds[end_time-TERM_NUM:end_time]) + yield pre_spd + diff --git a/demo/traffic_prediction/gen_result.py b/demo/traffic_prediction/gen_result.py new file mode 100644 index 0000000000..78e5bd7003 --- /dev/null +++ b/demo/traffic_prediction/gen_result.py @@ -0,0 +1,47 @@ +res = [] +with open('./rank-00000') as f: + for line in f: + pred = map(int,line.strip('\r\n;').split(";")) + #raw prediction range from 0 to 3 + res.append([i+1 for i in pred]) + +file_name = open('./data/pred.list').read().strip('\r\n') + +FORECASTING_NUM=24 +header=['id', + '201604200805', + '201604200810', + '201604200815', + '201604200820', + '201604200825', + '201604200830', + '201604200835', + '201604200840', + '201604200845', + '201604200850', + '201604200855', + '201604200900', + '201604200905', + '201604200910', + '201604200915', + '201604200920', + '201604200925', + '201604200930', + '201604200935', + '201604200940', + '201604200945', + '201604200950', + '201604200955', + '201604201000', + ] +################### +## To CSV format ## +################### +with open(file_name) as f: + f.next() + print ','.join(header) + for row_num, line in enumerate(f): + fields = line.rstrip('\r\n').split(',') + linkid = fields[0] + print linkid+','+','.join(map(str,res[row_num])) + diff --git a/demo/traffic_prediction/predict.sh b/demo/traffic_prediction/predict.sh new file mode 100755 index 0000000000..2cc709f109 --- /dev/null +++ b/demo/traffic_prediction/predict.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e + +cfg=trainer_config.py +# pass choice +model="output/pass-00000" +paddle train \ + --config=$cfg \ + --use_gpu=false \ + --job=test \ + --init_model_path=$model \ + --config_args=is_predict=1 \ + --predict_output_dir=. + +python gen_result.py > result.txt + +rm -rf rank-00000 diff --git a/demo/traffic_prediction/train.sh b/demo/traffic_prediction/train.sh new file mode 100755 index 0000000000..bd1a1036b8 --- /dev/null +++ b/demo/traffic_prediction/train.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e + +cfg=trainer_config.py +#TRAINER_BIN="./paddle_trainer" +paddle train \ + --config=$cfg \ + --save_dir=./output \ + --trainer_count=4 \ + --log_period=1000 \ + --dot_period=10 \ + --num_passes=10 \ + --use_gpu=false \ + --show_parameter_stats_period=3000 \ + --test_wait=1 + #--test_all_data_in_one_period=1 \ + 2>&1 | tee 'train.log' diff --git a/demo/traffic_prediction/trainer_config.py b/demo/traffic_prediction/trainer_config.py new file mode 100755 index 0000000000..835b1d688c --- /dev/null +++ b/demo/traffic_prediction/trainer_config.py @@ -0,0 +1,43 @@ +#!/usr/bin/env/python +#-*python-*- +from paddle.trainer_config_helpers import * + + +################################### DATA Configuration ############################################# +is_predict = get_config_arg('is_predict', bool, False) +trn = './data/train.list' if not is_predict else None +tst = './data/test.list' if not is_predict else './data/pred.list' +process = 'process' if not is_predict else 'process_predict' +define_py_data_sources2(train_list=trn, + test_list=tst, + module="dataprovider", + obj=process) +################################### Parameter Configuaration ####################################### +TERM_NUM=24 +FORECASTING_NUM= 25 +emb_size=16 +batch_size=128 if not is_predict else 1 +settings( + batch_size = batch_size, + learning_rate = 1e-3, + learning_method = RMSPropOptimizer() +) +################################### Algorithm Configuration ######################################## + +output_label = [] + +link_encode = data_layer(name='link_encode', size=TERM_NUM) +for i in xrange(FORECASTING_NUM): + # Each task share same weight. + link_param = ParamAttr(name='_link_vec.w', initial_max=1.0, initial_min=-1.0) + link_vec = fc_layer(input=link_encode,size=emb_size, param_attr=link_param) + score = fc_layer(input=link_vec, size=4, act=SoftmaxActivation()) + if is_predict: + maxid = maxid_layer(score) + output_label.append(maxid) + else: + # Multi-task training. + label = data_layer(name='label_%dmin'%((i+1)*5), size=4) + cls = classification_cost(input=score,name="cost_%dmin"%((i+1)*5), label=label) + output_label.append(cls) +outputs(output_label) From 82bee14dec310426d553c634bcfffc6293ad2f05 Mon Sep 17 00:00:00 2001 From: chengxingyi Date: Fri, 6 Jan 2017 16:46:04 +0800 Subject: [PATCH 108/119] A traffic prediction demo for ASC17 --- demo/traffic_prediction/data/get_data.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/traffic_prediction/data/get_data.sh b/demo/traffic_prediction/data/get_data.sh index 52cf6608df..716faac76f 100755 --- a/demo/traffic_prediction/data/get_data.sh +++ b/demo/traffic_prediction/data/get_data.sh @@ -27,8 +27,8 @@ wget http://paddlepaddle.bj.bcebos.com/demo/traffic/traffic_data.tar.gz echo "Unzipping..." tar -zxvf traffic_data.tar.gz -echo "data/speeds.csv" >> train.list -echo "data/speeds.csv" >> test.list -echo "data/speeds.csv" >> pred.list +echo "data/speeds.csv" > train.list +echo "data/speeds.csv" > test.list +echo "data/speeds.csv" > pred.list echo "Done." From a74f53651e133e79fdae5b290cc1a14c851c576f Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 6 Jan 2017 17:45:51 +0800 Subject: [PATCH 109/119] Format code --- demo/traffic_prediction/dataprovider.py | 31 +++++++----- demo/traffic_prediction/gen_result.py | 62 +++++++++++------------ demo/traffic_prediction/trainer_config.py | 32 ++++++------ 3 files changed, 64 insertions(+), 61 deletions(-) diff --git a/demo/traffic_prediction/dataprovider.py b/demo/traffic_prediction/dataprovider.py index bea0259b03..b915067260 100644 --- a/demo/traffic_prediction/dataprovider.py +++ b/demo/traffic_prediction/dataprovider.py @@ -18,6 +18,8 @@ import numpy as np TERM_NUM = 24 FORECASTING_NUM = 25 LABEL_VALUE_NUM = 4 + + def initHook(settings, file_list, **kwargs): """ Init hook is invoked before process data. It will set obj.slots and store data meta. @@ -27,8 +29,8 @@ def initHook(settings, file_list, **kwargs): :param file_list: the meta file object, which passed from trainer_config.py,but unused in this function. :param kwargs: unused other arguments. """ - del kwargs #unused - + del kwargs #unused + settings.pool_size = sys.maxint #Use a time seires of the past as feature. #Dense_vector's expression form is [float,float,...,float] @@ -38,40 +40,43 @@ def initHook(settings, file_list, **kwargs): for i in range(FORECASTING_NUM): settings.slots.append(integer_value(LABEL_VALUE_NUM)) -@provider(init_hook=initHook, cache=CacheType.CACHE_PASS_IN_MEM, should_shuffle=True) + +@provider( + init_hook=initHook, cache=CacheType.CACHE_PASS_IN_MEM, should_shuffle=True) def process(settings, file_name): with open(file_name) as f: #abandon fields name f.next() - for row_num, line in enumerate(f): - speeds = map(int,line.rstrip('\r\n').split(",")[1:]) + for row_num, line in enumerate(f): + speeds = map(int, line.rstrip('\r\n').split(",")[1:]) # Get the max index. end_time = len(speeds) # Scanning and generating samples - for i in range(TERM_NUM,end_time - FORECASTING_NUM): + for i in range(TERM_NUM, end_time - FORECASTING_NUM): # For dense slot - pre_spd = map(float,speeds[i-TERM_NUM:i]) + pre_spd = map(float, speeds[i - TERM_NUM:i]) # Integer value need predicting, values start from 0, so every one minus 1. - fol_spd = [i-1 for i in speeds[i:i + FORECASTING_NUM]] - + fol_spd = [i - 1 for i in speeds[i:i + FORECASTING_NUM]] + # Predicting label is missing, abandon the sample. if -1 in fol_spd: continue yield [pre_spd] + fol_spd + def predict_initHook(settings, file_list, **kwargs): settings.pool_size = sys.maxint settings.slots = [dense_vector(TERM_NUM)] -@provider(init_hook=predict_initHook,should_shuffle=False) + +@provider(init_hook=predict_initHook, should_shuffle=False) def process_predict(settings, file_name): with open(file_name) as f: #abandon fields name f.next() for row_num, line in enumerate(f): - speeds = map(int,line.rstrip('\r\n').split(",")) + speeds = map(int, line.rstrip('\r\n').split(",")) end_time = len(speeds) - pre_spd = map(float,speeds[end_time-TERM_NUM:end_time]) + pre_spd = map(float, speeds[end_time - TERM_NUM:end_time]) yield pre_spd - diff --git a/demo/traffic_prediction/gen_result.py b/demo/traffic_prediction/gen_result.py index 78e5bd7003..cb8f6e6832 100644 --- a/demo/traffic_prediction/gen_result.py +++ b/demo/traffic_prediction/gen_result.py @@ -1,39 +1,40 @@ res = [] with open('./rank-00000') as f: for line in f: - pred = map(int,line.strip('\r\n;').split(";")) + pred = map(int, line.strip('\r\n;').split(";")) #raw prediction range from 0 to 3 - res.append([i+1 for i in pred]) + res.append([i + 1 for i in pred]) file_name = open('./data/pred.list').read().strip('\r\n') -FORECASTING_NUM=24 -header=['id', - '201604200805', - '201604200810', - '201604200815', - '201604200820', - '201604200825', - '201604200830', - '201604200835', - '201604200840', - '201604200845', - '201604200850', - '201604200855', - '201604200900', - '201604200905', - '201604200910', - '201604200915', - '201604200920', - '201604200925', - '201604200930', - '201604200935', - '201604200940', - '201604200945', - '201604200950', - '201604200955', - '201604201000', - ] +FORECASTING_NUM = 24 +header = [ + 'id', + '201604200805', + '201604200810', + '201604200815', + '201604200820', + '201604200825', + '201604200830', + '201604200835', + '201604200840', + '201604200845', + '201604200850', + '201604200855', + '201604200900', + '201604200905', + '201604200910', + '201604200915', + '201604200920', + '201604200925', + '201604200930', + '201604200935', + '201604200940', + '201604200945', + '201604200950', + '201604200955', + '201604201000', +] ################### ## To CSV format ## ################### @@ -43,5 +44,4 @@ with open(file_name) as f: for row_num, line in enumerate(f): fields = line.rstrip('\r\n').split(',') linkid = fields[0] - print linkid+','+','.join(map(str,res[row_num])) - + print linkid + ',' + ','.join(map(str, res[row_num])) diff --git a/demo/traffic_prediction/trainer_config.py b/demo/traffic_prediction/trainer_config.py index 835b1d688c..c8755f7f3c 100755 --- a/demo/traffic_prediction/trainer_config.py +++ b/demo/traffic_prediction/trainer_config.py @@ -2,26 +2,22 @@ #-*python-*- from paddle.trainer_config_helpers import * - ################################### DATA Configuration ############################################# is_predict = get_config_arg('is_predict', bool, False) trn = './data/train.list' if not is_predict else None tst = './data/test.list' if not is_predict else './data/pred.list' process = 'process' if not is_predict else 'process_predict' -define_py_data_sources2(train_list=trn, - test_list=tst, - module="dataprovider", - obj=process) +define_py_data_sources2( + train_list=trn, test_list=tst, module="dataprovider", obj=process) ################################### Parameter Configuaration ####################################### -TERM_NUM=24 -FORECASTING_NUM= 25 -emb_size=16 -batch_size=128 if not is_predict else 1 +TERM_NUM = 24 +FORECASTING_NUM = 25 +emb_size = 16 +batch_size = 128 if not is_predict else 1 settings( - batch_size = batch_size, - learning_rate = 1e-3, - learning_method = RMSPropOptimizer() -) + batch_size=batch_size, + learning_rate=1e-3, + learning_method=RMSPropOptimizer()) ################################### Algorithm Configuration ######################################## output_label = [] @@ -29,15 +25,17 @@ output_label = [] link_encode = data_layer(name='link_encode', size=TERM_NUM) for i in xrange(FORECASTING_NUM): # Each task share same weight. - link_param = ParamAttr(name='_link_vec.w', initial_max=1.0, initial_min=-1.0) - link_vec = fc_layer(input=link_encode,size=emb_size, param_attr=link_param) + link_param = ParamAttr( + name='_link_vec.w', initial_max=1.0, initial_min=-1.0) + link_vec = fc_layer(input=link_encode, size=emb_size, param_attr=link_param) score = fc_layer(input=link_vec, size=4, act=SoftmaxActivation()) if is_predict: maxid = maxid_layer(score) output_label.append(maxid) else: # Multi-task training. - label = data_layer(name='label_%dmin'%((i+1)*5), size=4) - cls = classification_cost(input=score,name="cost_%dmin"%((i+1)*5), label=label) + label = data_layer(name='label_%dmin' % ((i + 1) * 5), size=4) + cls = classification_cost( + input=score, name="cost_%dmin" % ((i + 1) * 5), label=label) output_label.append(cls) outputs(output_label) From 3403c0068aeecd9a136c15081e5cb45cf50b64cd Mon Sep 17 00:00:00 2001 From: Yu Yang Date: Fri, 6 Jan 2017 17:50:53 +0800 Subject: [PATCH 110/119] tiny fixes --- demo/traffic_prediction/dataprovider.py | 8 ++++---- demo/traffic_prediction/gen_result.py | 14 ++++++++++++++ demo/traffic_prediction/trainer_config.py | 15 +++++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/demo/traffic_prediction/dataprovider.py b/demo/traffic_prediction/dataprovider.py index b915067260..19719350f2 100644 --- a/demo/traffic_prediction/dataprovider.py +++ b/demo/traffic_prediction/dataprovider.py @@ -34,11 +34,11 @@ def initHook(settings, file_list, **kwargs): settings.pool_size = sys.maxint #Use a time seires of the past as feature. #Dense_vector's expression form is [float,float,...,float] - settings.slots = [dense_vector(TERM_NUM)] + settings.input_types = [dense_vector(TERM_NUM)] #There are next FORECASTING_NUM fragments you need predict. #Every predicted condition at time point has four states. for i in range(FORECASTING_NUM): - settings.slots.append(integer_value(LABEL_VALUE_NUM)) + settings.input_types.append(integer_value(LABEL_VALUE_NUM)) @provider( @@ -57,7 +57,7 @@ def process(settings, file_name): pre_spd = map(float, speeds[i - TERM_NUM:i]) # Integer value need predicting, values start from 0, so every one minus 1. - fol_spd = [i - 1 for i in speeds[i:i + FORECASTING_NUM]] + fol_spd = [j - 1 for j in speeds[i:i + FORECASTING_NUM]] # Predicting label is missing, abandon the sample. if -1 in fol_spd: @@ -67,7 +67,7 @@ def process(settings, file_name): def predict_initHook(settings, file_list, **kwargs): settings.pool_size = sys.maxint - settings.slots = [dense_vector(TERM_NUM)] + settings.input_types = [dense_vector(TERM_NUM)] @provider(init_hook=predict_initHook, should_shuffle=False) diff --git a/demo/traffic_prediction/gen_result.py b/demo/traffic_prediction/gen_result.py index cb8f6e6832..d6c1b03370 100644 --- a/demo/traffic_prediction/gen_result.py +++ b/demo/traffic_prediction/gen_result.py @@ -1,3 +1,17 @@ +# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + res = [] with open('./rank-00000') as f: for line in f: diff --git a/demo/traffic_prediction/trainer_config.py b/demo/traffic_prediction/trainer_config.py index c8755f7f3c..bb6a4ac987 100755 --- a/demo/traffic_prediction/trainer_config.py +++ b/demo/traffic_prediction/trainer_config.py @@ -1,5 +1,16 @@ -#!/usr/bin/env/python -#-*python-*- +# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from paddle.trainer_config_helpers import * ################################### DATA Configuration ############################################# From f45b45e2441854f1d026b0e1b030ff6b057cb70b Mon Sep 17 00:00:00 2001 From: chengxingyi Date: Fri, 6 Jan 2017 19:46:49 +0800 Subject: [PATCH 111/119] A traffic prediction demo for ASC17 --- demo/traffic_prediction/data/get_data.sh | 4 ++-- demo/traffic_prediction/dataprovider.py | 4 ++-- demo/traffic_prediction/gen_result.py | 2 +- demo/traffic_prediction/predict.sh | 2 +- demo/traffic_prediction/train.sh | 5 +---- demo/traffic_prediction/trainer_config.py | 4 ++-- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/demo/traffic_prediction/data/get_data.sh b/demo/traffic_prediction/data/get_data.sh index 716faac76f..f2fa548d47 100755 --- a/demo/traffic_prediction/data/get_data.sh +++ b/demo/traffic_prediction/data/get_data.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# Copyright (c) 2016 PaddlePaddle Authors, Inc. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ cd $DIR #download the dataset echo "Downloading traffic data..." -wget http://paddlepaddle.bj.bcebos.com/demo/traffic/traffic_data.tar.gz +wget http://paddlepaddle.cdn.bcebos.com/demo/traffic/traffic_data.tar.gz #extract package echo "Unzipping..." diff --git a/demo/traffic_prediction/dataprovider.py b/demo/traffic_prediction/dataprovider.py index 19719350f2..c7883b6950 100644 --- a/demo/traffic_prediction/dataprovider.py +++ b/demo/traffic_prediction/dataprovider.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# Copyright (c) 2016 PaddlePaddle Authors, Inc. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ from paddle.trainer.PyDataProvider2 import * import sys import numpy as np TERM_NUM = 24 -FORECASTING_NUM = 25 +FORECASTING_NUM = 24 LABEL_VALUE_NUM = 4 diff --git a/demo/traffic_prediction/gen_result.py b/demo/traffic_prediction/gen_result.py index d6c1b03370..3da70b3031 100644 --- a/demo/traffic_prediction/gen_result.py +++ b/demo/traffic_prediction/gen_result.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# Copyright (c) 2016 PaddlePaddle Authors, Inc. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/demo/traffic_prediction/predict.sh b/demo/traffic_prediction/predict.sh index 2cc709f109..cec35dce11 100755 --- a/demo/traffic_prediction/predict.sh +++ b/demo/traffic_prediction/predict.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# Copyright (c) 2016 PaddlePaddle Authors, Inc. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/demo/traffic_prediction/train.sh b/demo/traffic_prediction/train.sh index bd1a1036b8..48dfc5604f 100755 --- a/demo/traffic_prediction/train.sh +++ b/demo/traffic_prediction/train.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# Copyright (c) 2016 PaddlePaddle Authors, Inc. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ set -e cfg=trainer_config.py -#TRAINER_BIN="./paddle_trainer" paddle train \ --config=$cfg \ --save_dir=./output \ @@ -25,6 +24,4 @@ paddle train \ --num_passes=10 \ --use_gpu=false \ --show_parameter_stats_period=3000 \ - --test_wait=1 - #--test_all_data_in_one_period=1 \ 2>&1 | tee 'train.log' diff --git a/demo/traffic_prediction/trainer_config.py b/demo/traffic_prediction/trainer_config.py index bb6a4ac987..52d678624a 100755 --- a/demo/traffic_prediction/trainer_config.py +++ b/demo/traffic_prediction/trainer_config.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Baidu, Inc. All Rights Reserved +# Copyright (c) 2016 PaddlePaddle Authors, Inc. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ define_py_data_sources2( train_list=trn, test_list=tst, module="dataprovider", obj=process) ################################### Parameter Configuaration ####################################### TERM_NUM = 24 -FORECASTING_NUM = 25 +FORECASTING_NUM = 24 emb_size = 16 batch_size = 128 if not is_predict else 1 settings( From c24e94c8a4c29f35019fad353760926c291ab3d6 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 8 Jan 2017 09:25:38 +0800 Subject: [PATCH 112/119] Check python if system already equipped one --- .travis.yml | 6 +- cmake/configure.cmake | 4 + cmake/external/python.cmake | 386 ++++++++++---------- paddle/api/CMakeLists.txt | 26 +- paddle/scripts/travis/before_install.osx.sh | 4 +- paddle/scripts/travis/build_and_test.sh | 16 +- paddle/utils/PythonUtil.cpp.in | 6 +- 7 files changed, 236 insertions(+), 212 deletions(-) diff --git a/.travis.yml b/.travis.yml index bc91855a85..eecf5e81f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,13 +21,14 @@ addons: packages: - gcc-4.8 - g++-4.8 - - wget - git - build-essential - libatlas-base-dev - python - python-pip - python2.7-dev + - python-numpy + - python-wheel - curl - swig - graphviz @@ -48,8 +49,7 @@ before_install: fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then paddle/scripts/travis/before_install.osx.sh; fi - if [[ "$JOB" == "PRE_COMMIT" ]]; then sudo ln -s /usr/bin/clang-format-3.8 /usr/bin/clang-format; fi - - pip install --upgrade pip - - pip install wheel protobuf sphinx recommonmark sphinx_rtd_theme virtualenv pre-commit requests==2.9.2 LinkChecker + - pip install numpy wheel protobuf sphinx recommonmark sphinx_rtd_theme virtualenv pre-commit requests==2.9.2 LinkChecker script: - paddle/scripts/travis/main.sh notifications: diff --git a/cmake/configure.cmake b/cmake/configure.cmake index ae0ec01d94..0bb016201d 100644 --- a/cmake/configure.cmake +++ b/cmake/configure.cmake @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +if(NOT WITH_PYTHON) + add_definitions(-DPADDLE_NO_PYTHON) +endif(NOT WITH_PYTHON) + if(WITH_DSO) add_definitions(-DPADDLE_USE_DSO) endif(WITH_DSO) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index e4c570479f..357ee901ce 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -13,192 +13,210 @@ # limitations under the License. INCLUDE(ExternalProject) +INCLUDE(python_module) + +FIND_PACKAGE(PythonInterp 2.7) +FIND_PACKAGE(PythonLibs 2.7) + +SET(py_env PATH=${PATH} PYTHONHOME=${PYTHONHOME} PYTHONPATH=${PYTHONPATH}) + +IF(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) + find_python_module(pip REQUIRED) + find_python_module(numpy REQUIRED) + find_python_module(wheel REQUIRED) + find_python_module(google.protobuf REQUIRED) + FIND_PACKAGE(NumPy REQUIRED) +ELSE(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) + ##################################### PYTHON ######################################## + SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/python) + SET(PYTHON_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/python) + SET(_python_DIR ${PYTHON_INSTALL_DIR}) + + IF(UNIX) + SET(PYTHON_FOUND ON) + SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include/python2.7" CACHE PATH "Python include dir" FORCE) + SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/lib/libpython2.7.a" CACHE FILEPATH "Python library" FORCE) + SET(PYTHON_EXECUTABLE ${PYTHON_INSTALL_DIR}/bin/python CACHE FILEPATH "Python executable" FORCE) + SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/lib/python2.7/site-packages" CACHE PATH "Python site-packages path" FORCE) + ELSEIF(WIN32) + SET(PYTHON_FOUND ON) + SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include" CACHE PATH "Python include dir" FORCE) + SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/libs/python27.lib" CACHE FILEPATH "Python library" FORCE) + SET(PYTHON_EXECUTABLE "${PYTHON_INSTALL_DIR}/bin/python.exe" CACHE FILEPATH "Python executable" FORCE) + SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/Lib/site-packages" CACHE PATH "Python site-packages path" FORCE) + ELSE() + MESSAGE(FATAL_ERROR "Unknown system !") + ENDIF() - -##################################### PYTHON ######################################## -SET(PYTHON_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/python) -SET(PYTHON_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/install/python) -SET(_python_DIR ${PYTHON_INSTALL_DIR}) - -IF(UNIX) - SET(PYTHON_FOUND ON) - SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include/python2.7" CACHE PATH "Python include dir" FORCE) - SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/lib/libpython2.7.a" CACHE FILEPATH "Python library" FORCE) - SET(PYTHON_EXECUTABLE ${PYTHON_INSTALL_DIR}/bin/python CACHE FILEPATH "Python executable" FORCE) - SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/lib/python2.7/site-packages" CACHE PATH "Python site-packages path" FORCE) -ELSEIF(WIN32) - SET(PYTHON_FOUND ON) - SET(PYTHON_INCLUDE_DIR "${PYTHON_INSTALL_DIR}/include" CACHE PATH "Python include dir" FORCE) - SET(PYTHON_LIBRARIES "${PYTHON_INSTALL_DIR}/libs/python27.lib" CACHE FILEPATH "Python library" FORCE) - SET(PYTHON_EXECUTABLE "${PYTHON_INSTALL_DIR}/bin/python.exe" CACHE FILEPATH "Python executable" FORCE) - SET(PY_SITE_PACKAGES_PATH "${PYTHON_INSTALL_DIR}/Lib/site-packages" CACHE PATH "Python site-packages path" FORCE) -ELSE() - MESSAGE(FATAL_ERROR "Unknown system !") -ENDIF() - -SET(py_env - PATH=${PYTHON_INSTALL_DIR}/bin/:$ENV{PATH} - PYTHONHOME=${PYTHON_INSTALL_DIR} - PYTHONPATH=${PYTHON_INSTALL_DIR}/lib:${PYTHON_INSTALL_DIR}/lib/python2.7:${PY_SITE_PACKAGES_PATH}) - -INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) - -IF(APPLE) - LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS - -DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON - ) -ENDIF() - -SET(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) - -# Force Python build to "Release". -IF(CMAKE_CONFIGURATION_TYPES) - SET(SAVED_CMAKE_CFG_INTDIR ${CMAKE_CFG_INTDIR}) - SET(CMAKE_CFG_INTDIR "Release") -ELSE() - LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS - -DCMAKE_BUILD_TYPE:STRING=Release - ) -ENDIF() - -ExternalProject_Add(python - ${EXTERNAL_PROJECT_LOG_ARGS} - GIT_REPOSITORY "https://github.com/python-cmake-buildsystem/python-cmake-buildsystem.git" - PREFIX ${PYTHON_SOURCES_DIR} - UPDATE_COMMAND "" - CMAKE_ARGS -DPYTHON_VERSION=2.7.12 - CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - CMAKE_CACHE_ARGS - -DCMAKE_INSTALL_PREFIX:PATH=${PYTHON_INSTALL_DIR} - -DBUILD_LIBPYTHON_SHARED:BOOL=OFF - -DUSE_SYSTEM_LIBRARIES:BOOL=OFF - -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} - -DZLIB_INCLUDE_DIR:PATH=${ZLIB_INCLUDE_DIR} - -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} - -DDOWNLOAD_SOURCES:BOOL=ON - -DINSTALL_WINDOWS_TRADITIONAL:BOOL=OFF - ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} - ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS} - DEPENDS zlib -) -#################################################################################### - -##################################### SETUPTOOLS ################################### -SET(SETUPTOOLS_SOURCES_DIR ${PYTHON_SOURCES_DIR}/setuptools) -ExternalProject_Add(setuptools - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${SETUPTOOLS_SOURCES_DIR} - URL "https://pypi.python.org/packages/source/s/setuptools/setuptools-18.3.2.tar.gz" - BUILD_IN_SOURCE 1 - PATCH_COMMAND "" - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python zlib -) -##################################################################################### - -##################################### SIX ########################################### -SET(SIX_SOURCES_DIR ${PYTHON_SOURCES_DIR}/six) -ExternalProject_Add(six - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${SIX_SOURCES_DIR} - URL https://pypi.python.org/packages/source/s/six/six-1.10.0.tar.gz - BUILD_IN_SOURCE 1 - PATCH_COMMAND "" - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python setuptools -) -##################################################################################### - -##################################### CYTHON ######################################## -SET(CYTHON_SOURCES_DIR ${PYTHON_SOURCES_DIR}/cython) -ExternalProject_Add(cython - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${CYTHON_SOURCES_DIR} - URL https://github.com/cython/cython/archive/0.25.2.tar.gz - GIT_TAG 0.25.2 - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - PATCH_COMMAND "" - UPDATE_COMMAND "" - INSTALL_COMMAND "" - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python -) -#################################################################################### - -##################################### NUMPY ######################################## -SET(NUMPY_SOURCES_DIR ${PYTHON_SOURCES_DIR}/numpy) -SET(NUMPY_TAG_VERSION "v1.11.3") -SET(NUMPY_VERSION "1.11.3") - -SET(EGG_NAME "") -SET(PYTHON_NUMPY_INCLUDE_DIR "") -IF(WIN32) - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}.egg") -ELSE(WIN32) IF(APPLE) - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}-${MACOS_VERSION}") - ELSE(APPLE) - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux") - SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux") - ENDIF(APPLE) - - FOREACH(suffix x86_64 intel fat64 fat32 universal) - LIST(APPEND PYTHON_NUMPY_INCLUDE_DIR ${PY_SITE_PACKAGES_PATH}/${EGG_NAME}-${suffix}.egg/numpy/core/include) - ENDFOREACH() -ENDIF(WIN32) + LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS + -DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON + ) + ENDIF() + + SET(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) + + # Force Python build to "Release". + IF(CMAKE_CONFIGURATION_TYPES) + SET(SAVED_CMAKE_CFG_INTDIR ${CMAKE_CFG_INTDIR}) + SET(CMAKE_CFG_INTDIR "Release") + ELSE() + LIST(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS + -DCMAKE_BUILD_TYPE:STRING=Release + ) + ENDIF() + + ExternalProject_Add(python + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY "https://github.com/python-cmake-buildsystem/python-cmake-buildsystem.git" + PREFIX ${PYTHON_SOURCES_DIR} + UPDATE_COMMAND "" + CMAKE_ARGS -DPYTHON_VERSION=2.7.12 + CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + CMAKE_CACHE_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${PYTHON_INSTALL_DIR} + -DBUILD_LIBPYTHON_SHARED:BOOL=OFF + -DUSE_SYSTEM_LIBRARIES:BOOL=OFF + -DZLIB_ROOT:FILEPATH=${ZLIB_ROOT} + -DZLIB_INCLUDE_DIR:PATH=${ZLIB_INCLUDE_DIR} + -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} + -DDOWNLOAD_SOURCES:BOOL=ON + -DINSTALL_WINDOWS_TRADITIONAL:BOOL=OFF + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_ARGS} + DEPENDS zlib + ) + + SET(py_env + PATH=${PYTHON_INSTALL_DIR}/bin + PYTHONHOME=${PYTHON_INSTALL_DIR} + PYTHONPATH=${PYTHON_INSTALL_DIR}/lib:${PYTHON_INSTALL_DIR}/lib/python2.7:${PY_SITE_PACKAGES_PATH}) + #################################################################################### + + ##################################### SETUPTOOLS ################################### + SET(SETUPTOOLS_SOURCES_DIR ${PYTHON_SOURCES_DIR}/setuptools) + ExternalProject_Add(setuptools + ${EXTERNAL_PROJECT_LOG_ARGS} + PREFIX ${SETUPTOOLS_SOURCES_DIR} + URL "https://pypi.python.org/packages/source/s/setuptools/setuptools-18.3.2.tar.gz" + BUILD_IN_SOURCE 1 + PATCH_COMMAND "" + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python zlib + ) + ##################################################################################### + + ##################################### SIX ########################################### + SET(SIX_SOURCES_DIR ${PYTHON_SOURCES_DIR}/six) + ExternalProject_Add(six + ${EXTERNAL_PROJECT_LOG_ARGS} + PREFIX ${SIX_SOURCES_DIR} + URL https://pypi.python.org/packages/source/s/six/six-1.10.0.tar.gz + BUILD_IN_SOURCE 1 + PATCH_COMMAND "" + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python setuptools + ) + ##################################################################################### + + ##################################### CYTHON ######################################## + SET(CYTHON_SOURCES_DIR ${PYTHON_SOURCES_DIR}/cython) + ExternalProject_Add(cython + ${EXTERNAL_PROJECT_LOG_ARGS} + PREFIX ${CYTHON_SOURCES_DIR} + URL https://github.com/cython/cython/archive/0.25.2.tar.gz + GIT_TAG 0.25.2 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND "" + PATCH_COMMAND "" + UPDATE_COMMAND "" + INSTALL_COMMAND "" + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python + ) + #################################################################################### + + ##################################### NUMPY ######################################## + SET(NUMPY_SOURCES_DIR ${PYTHON_SOURCES_DIR}/numpy) + SET(NUMPY_TAG_VERSION "v1.11.3") + SET(NUMPY_VERSION "1.11.3") + + SET(EGG_NAME "") + SET(PYTHON_NUMPY_INCLUDE_DIR "") + IF(WIN32) + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}.egg") + ELSE(WIN32) + IF(APPLE) + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-${HOST_SYSTEM}-${MACOS_VERSION}") + ELSE(APPLE) + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux") + SET(EGG_NAME "numpy-${NUMPY_VERSION}-py2.7-linux") + ENDIF(APPLE) + + FOREACH(suffix x86_64 intel fat64 fat32 universal) + LIST(APPEND PYTHON_NUMPY_INCLUDE_DIR ${PY_SITE_PACKAGES_PATH}/${EGG_NAME}-${suffix}.egg/numpy/core/include) + ENDFOREACH() + ENDIF(WIN32) + + ExternalProject_Add(numpy + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY https://github.com/numpy/numpy.git + GIT_TAG ${NUMPY_TAG_VERSION} + CONFIGURE_COMMAND "" + UPDATE_COMMAND "" + PREFIX ${NUMPY_SOURCES_DIR} + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py build + INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools cython + ) + #################################################################################### + + ##################################### WHEEL ######################################## + SET(WHEEL_SOURCES_DIR ${PYTHON_SOURCES_DIR}/wheel) + ExternalProject_Add(wheel + ${EXTERNAL_PROJECT_LOG_ARGS} + URL https://pypi.python.org/packages/source/w/wheel/wheel-0.29.0.tar.gz + PREFIX ${WHEEL_SOURCES_DIR} + CONFIGURE_COMMAND "" + UPDATE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools + ) + #################################################################################### + + ################################### PROTOBUF ####################################### + SET(PY_PROTOBUF_SOURCES_DIR ${PYTHON_SOURCES_DIR}/protobuf) + ExternalProject_Add(python-protobuf + ${EXTERNAL_PROJECT_LOG_ARGS} + URL https://pypi.python.org/packages/e0/b0/0a1b364fe8a7d177b4b7d4dca5b798500dc57a7273b93cca73931b305a6a/protobuf-3.1.0.post1.tar.gz + URL_MD5 38b5fb160c768d2f8444d0c6d637ff91 + PREFIX ${PY_PROTOBUF_SOURCES_DIR} + BUILD_IN_SOURCE 1 + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py build + INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + DEPENDS python setuptools six + ) + #################################################################################### + + LIST(APPEND external_project_dependencies python setuptools six cython wheel python-protobuf numpy) + +ENDIF(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) +INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) -ExternalProject_Add(numpy - ${EXTERNAL_PROJECT_LOG_ARGS} - GIT_REPOSITORY https://github.com/numpy/numpy.git - GIT_TAG ${NUMPY_TAG_VERSION} - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - PREFIX ${NUMPY_SOURCES_DIR} - BUILD_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py build - INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - BUILD_IN_SOURCE 1 - DEPENDS python setuptools cython -) -#################################################################################### - -##################################### WHEEL ######################################## -SET(WHEEL_SOURCES_DIR ${PYTHON_SOURCES_DIR}/wheel) -ExternalProject_Add(wheel - ${EXTERNAL_PROJECT_LOG_ARGS} - URL https://pypi.python.org/packages/source/w/wheel/wheel-0.29.0.tar.gz - PREFIX ${WHEEL_SOURCES_DIR} - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - BUILD_IN_SOURCE 1 - DEPENDS python setuptools -) -#################################################################################### - -################################### PROTOBUF ####################################### -SET(PY_PROTOBUF_SOURCES_DIR ${PYTHON_SOURCES_DIR}/protobuf) -ExternalProject_Add(python-protobuf - ${EXTERNAL_PROJECT_LOG_ARGS} - URL https://pypi.python.org/packages/e0/b0/0a1b364fe8a7d177b4b7d4dca5b798500dc57a7273b93cca73931b305a6a/protobuf-3.1.0.post1.tar.gz - URL_MD5 38b5fb160c768d2f8444d0c6d637ff91 - PREFIX ${PY_PROTOBUF_SOURCES_DIR} - BUILD_IN_SOURCE 1 - PATCH_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND env PATH=${PROTOBUF_INSTALL_DIR}/bin:$ENV{PATH} ${py_env} ${PYTHON_EXECUTABLE} setup.py build - INSTALL_COMMAND env PATH=${PROTOBUF_INSTALL_DIR}/bin:$ENV{PATH} ${py_env} ${PYTHON_EXECUTABLE} setup.py install - DEPENDS python setuptools six -) - -LIST(APPEND external_project_dependencies python setuptools six cython numpy wheel python-protobuf) +MESSAGE("[Paddle] Python Executable: ${PYTHON_EXECUTABLE}") +MESSAGE("[Paddle] Python Include: ${PYTHON_INCLUDE_DIRS}") +MESSAGE("[Paddle] Python Libraries: ${PYTHON_LIBRARIES}") diff --git a/paddle/api/CMakeLists.txt b/paddle/api/CMakeLists.txt index 3ac50e34bb..6e8fcd114d 100644 --- a/paddle/api/CMakeLists.txt +++ b/paddle/api/CMakeLists.txt @@ -94,17 +94,19 @@ add_dependencies(python_api_wheel python_swig_sources paddle_cuda) if(WITH_TESTING) - SET(PIP_SOURCES_DIR ${PYTHON_SOURCES_DIR}/pip) - ExternalProject_Add(pip - ${EXTERNAL_PROJECT_LOG_ARGS} - GIT_REPOSITORY https://github.com/pypa/pip.git - GIT_TAG 9.0.1 - PREFIX ${PIP_SOURCES_DIR} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install - BUILD_IN_SOURCE 1 - DEPENDS python setuptools python_api_wheel - ) + IF(NOT PY_PIP_FOUND) + SET(PIP_SOURCES_DIR ${PYTHON_SOURCES_DIR}/pip) + ExternalProject_Add(pip + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY https://github.com/pypa/pip.git + GIT_TAG 9.0.1 + PREFIX ${PIP_SOURCES_DIR} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND env ${py_env} ${PYTHON_EXECUTABLE} setup.py install + BUILD_IN_SOURCE 1 + DEPENDS python setuptools python_api_wheel + ) + ENDIF() add_subdirectory(test) endif() diff --git a/paddle/scripts/travis/before_install.osx.sh b/paddle/scripts/travis/before_install.osx.sh index fd113d313e..7036f971fd 100755 --- a/paddle/scripts/travis/before_install.osx.sh +++ b/paddle/scripts/travis/before_install.osx.sh @@ -1,4 +1,6 @@ #!/bin/bash brew update brew tap homebrew/science -brew install openblas md5sha1sum +brew install python +sudo pip install --upgrade protobuf +brew install swig openblas md5sha1sum protobuf diff --git a/paddle/scripts/travis/build_and_test.sh b/paddle/scripts/travis/build_and_test.sh index ffc48eae66..07624ec719 100755 --- a/paddle/scripts/travis/build_and_test.sh +++ b/paddle/scripts/travis/build_and_test.sh @@ -1,23 +1,19 @@ #!/bin/bash source ./common.sh -python -c 'import pip; print(pip.pep425tags.get_supported())' - -if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - CMAKE_EXTRA="-DWITH_SWIG_PY=OFF" -else - CMAKE_EXTRA="-DWITH_SWIG_PY=ON" -fi - -cmake .. -DWITH_GPU=OFF -DWITH_DOC=OFF -DWITH_TESTING=ON -DON_TRAVIS=ON -DON_COVERALLS=ON ${CMAKE_EXTRA} - NPROC=1 if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + export PYTHONPATH=/opt/python/2.7.12/lib/python2.7/site-packages + export PYTHONHOME=/opt/python/2.7.12 + export PATH=/opt/python/2.7.12/bin:${PATH} + cmake .. -DON_TRAVIS=ON -DON_COVERALLS=ON -DCOVERALLS_UPLOAD=ON NRPOC=`nproc` make -j $NPROC make coveralls sudo make install elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + export PYTHONPATH=/usr/local/lib/python2.7/site-packages + cmake .. -DON_TRAVIS=ON -DON_COVERALLS=ON -DCOVERALLS_UPLOAD=ON -DWITH_SWIG_PY=ON NPROC=`sysctl -n hw.ncpu` make -j $NPROC env CTEST_OUTPUT_ON_FAILURE=1 make test ARGS="-j $NPROC" diff --git a/paddle/utils/PythonUtil.cpp.in b/paddle/utils/PythonUtil.cpp.in index e0caaf4cd6..66b5795e29 100644 --- a/paddle/utils/PythonUtil.cpp.in +++ b/paddle/utils/PythonUtil.cpp.in @@ -195,8 +195,10 @@ extern const char enable_virtualenv_py[]; } void initPython(int argc, char** argv) { #ifndef PADDLE_NO_PYTHON - char PythonHome[] = "@PYTHON_INSTALL_DIR@"; // NOLINT - Py_SetPythonHome(PythonHome); + char pyHome[] = "@PYTHON_INSTALL_DIR@"; // NOLINT + if (strlen(pyHome)) { + Py_SetPythonHome(pyHome); + } Py_SetProgramName(argv[0]); Py_Initialize(); PySys_SetArgv(argc, argv); From 5788a879d4480a010810cd0d6a6900a1e50c9853 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 8 Jan 2017 09:25:55 +0800 Subject: [PATCH 113/119] Polish cmake configuration --- CMakeLists.txt | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ed757bd1b..ede4af3e3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,21 +28,21 @@ include(system) include(simd) ###################### Configurations ############################ -option(WITH_DSO "Compile PaddlePaddle with dynamic linked libraries" ON) -option(WITH_GPU "Compile PaddlePaddle with gpu" ${CUDA_FOUND}) -option(WITH_DOUBLE "Compile PaddlePaddle with double precision, otherwise use single precision" OFF) -option(WITH_AVX "Compile PaddlePaddle with avx intrinsics" ${AVX_FOUND}) -option(WITH_PYTHON "Compile PaddlePaddle with python interpreter" ON) -option(WITH_STYLE_CHECK "Style Check for PaddlePaddle" ON) -option(WITH_RDMA "Compile PaddlePaddle with rdma support" OFF) -option(WITH_TIMER "Compile PaddlePaddle use timer" OFF) -option(WITH_PROFILER "Compile PaddlePaddle use gpu profiler" OFF) -option(WITH_TESTING "Compile and run unittest for PaddlePaddle" ON) -option(WITH_DOC "Compile PaddlePaddle with documentation" OFF) -option(WITH_SWIG_PY "Compile PaddlePaddle with py PaddlePaddle prediction api" ON) -option(ON_TRAVIS "Running test on travis-ci or not." OFF) -option(ON_COVERALLS "Generating code coverage data on coveralls or not." OFF) -option(COVERALLS_UPLOAD "Uploading the generated coveralls json." ON) +option(WITH_GPU "Compile PaddlePaddle with NVIDIA GPU" ${CUDA_FOUND}) +option(WITH_AVX "Compile PaddlePaddle with AVX intrinsics" ${AVX_FOUND}) +option(WITH_DSO "Compile PaddlePaddle with dynamic linked CUDA" ON) +option(WITH_TESTING "Compile PaddlePaddle with unit testing" ON) +option(WITH_SWIG_PY "Compile PaddlePaddle with inference api" ON) +option(WITH_STYLE_CHECK "Compile PaddlePaddle with style check" ON) +option(WITH_PYTHON "Compile PaddlePaddle with python interpreter" ON) +option(WITH_DOUBLE "Compile PaddlePaddle with double precision" OFF) +option(WITH_RDMA "Compile PaddlePaddle with RDMA support" OFF) +option(WITH_TIMER "Compile PaddlePaddle with stats timer" OFF) +option(WITH_PROFILER "Compile PaddlePaddle with GPU profiler" OFF) +option(WITH_DOC "Compile PaddlePaddle with documentation" OFF) +option(ON_COVERALLS "Compile PaddlePaddle with code coverage" OFF) +option(COVERALLS_UPLOAD "Package code coverage data to coveralls" OFF) +option(ON_TRAVIS "Exclude special unit test on Travis CI" OFF) include(external/zlib) # download, build, install zlib include(external/gflags) # download, build, install gflags @@ -63,7 +63,6 @@ include(flags) # set paddle compile flags include(cudnn) # set cudnn libraries include(version) # set PADDLE_VERSION include(coveralls) # set code coverage -include(python_module) # set python module include(configure) # add paddle env configuration From 635d4622a7750a7d5640c1539c79dc8c0a1b1f73 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 8 Jan 2017 09:33:11 +0800 Subject: [PATCH 114/119] Clean py_env --- cmake/external/python.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/external/python.cmake b/cmake/external/python.cmake index 357ee901ce..cbb6940221 100644 --- a/cmake/external/python.cmake +++ b/cmake/external/python.cmake @@ -18,7 +18,7 @@ INCLUDE(python_module) FIND_PACKAGE(PythonInterp 2.7) FIND_PACKAGE(PythonLibs 2.7) -SET(py_env PATH=${PATH} PYTHONHOME=${PYTHONHOME} PYTHONPATH=${PYTHONPATH}) +SET(py_env "") IF(PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) find_python_module(pip REQUIRED) From 9a7df696d97b9e1acdd5d41ad6175e2cb21757fd Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 8 Jan 2017 11:53:05 +0800 Subject: [PATCH 115/119] Clean travis ci --- paddle/scripts/travis/build_and_test.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/paddle/scripts/travis/build_and_test.sh b/paddle/scripts/travis/build_and_test.sh index 07624ec719..33eb0207ea 100755 --- a/paddle/scripts/travis/build_and_test.sh +++ b/paddle/scripts/travis/build_and_test.sh @@ -16,7 +16,4 @@ elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cmake .. -DON_TRAVIS=ON -DON_COVERALLS=ON -DCOVERALLS_UPLOAD=ON -DWITH_SWIG_PY=ON NPROC=`sysctl -n hw.ncpu` make -j $NPROC - env CTEST_OUTPUT_ON_FAILURE=1 make test ARGS="-j $NPROC" - sudo make install - sudo paddle version fi From 934ba0bf3985472135df219bfbd13783d161411d Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 8 Jan 2017 12:59:33 +0800 Subject: [PATCH 116/119] Disable dynamic linked cuda libs default --- CMakeLists.txt | 2 +- cmake/util.cmake | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ed757bd1b..804fe43592 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ include(system) include(simd) ###################### Configurations ############################ -option(WITH_DSO "Compile PaddlePaddle with dynamic linked libraries" ON) +option(WITH_DSO "Compile PaddlePaddle with dynamic linked libraries" OFF) option(WITH_GPU "Compile PaddlePaddle with gpu" ${CUDA_FOUND}) option(WITH_DOUBLE "Compile PaddlePaddle with double precision, otherwise use single precision" OFF) option(WITH_AVX "Compile PaddlePaddle with avx intrinsics" ${AVX_FOUND}) diff --git a/cmake/util.cmake b/cmake/util.cmake index a19bf2a799..a6cb74853e 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -120,6 +120,11 @@ function(link_paddle_exe TARGET_NAME) target_link_libraries(${TARGET_NAME} rt) endif() endif() + + if(NOT WITH_DSO) + target_link_libraries(${TARGET_NAME} ${WARPCTC_LIBRARIES}) + endif() + add_dependencies(${TARGET_NAME} ${external_project_dependencies}) endfunction() From 057c216e000f3728f1fb3b585e5b2bd930e9dc77 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 8 Jan 2017 14:41:11 +0800 Subject: [PATCH 117/119] Fix warpctc static libs and torch --- cmake/external/warpctc.cmake | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index 34397dca7a..f924aa193f 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -24,16 +24,10 @@ SET(WARPCTC_LIB_DIR "${WARPCTC_INSTALL_DIR}/lib" CACHE PATH "Warp-ctc Library Di IF(WIN32) SET(WARPCTC_LIBRARIES - "${WARPCTC_INSTALL_DIR}/lib/warpctc.dll" CACHE FILEPATH "Warp-ctc Library" FORCE) + "${WARPCTC_INSTALL_DIR}/lib/warpctc.lib" CACHE FILEPATH "Warp-ctc Library" FORCE) ELSE(WIN32) - IF(APPLE) - SET(_warpctc_SHARED_SUFFIX dylib) - ELSE(APPLE) - SET(_warpctc_SHARED_SUFFIX so) - ENDIF(APPLE) - SET(WARPCTC_LIBRARIES - "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.${_warpctc_SHARED_SUFFIX}" CACHE FILEPATH "Warp-ctc Library" FORCE) + "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.a" CACHE FILEPATH "Warp-ctc Library" FORCE) ENDIF(WIN32) IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" ) @@ -53,6 +47,7 @@ ExternalProject_Add( CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${WARPCTC_INSTALL_DIR} CMAKE_ARGS -DWITH_GPU=${WITH_GPU} CMAKE_ARGS -DWITH_OMP=${USE_OMP} + CMAKE_ARGS -DWITH_TORCH=OFF CMAKE_ARGS -DBUILD_SHARED=OFF ) From 425f9515f03cda2e4f44d10c55a7371fa7c08817 Mon Sep 17 00:00:00 2001 From: liaogang Date: Sun, 8 Jan 2017 15:33:46 +0800 Subject: [PATCH 118/119] Fix warpctc bugs --- cmake/external/warpctc.cmake | 12 +++++++++--- paddle/gserver/tests/CMakeLists.txt | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cmake/external/warpctc.cmake b/cmake/external/warpctc.cmake index f924aa193f..7386d935b8 100644 --- a/cmake/external/warpctc.cmake +++ b/cmake/external/warpctc.cmake @@ -24,10 +24,16 @@ SET(WARPCTC_LIB_DIR "${WARPCTC_INSTALL_DIR}/lib" CACHE PATH "Warp-ctc Library Di IF(WIN32) SET(WARPCTC_LIBRARIES - "${WARPCTC_INSTALL_DIR}/lib/warpctc.lib" CACHE FILEPATH "Warp-ctc Library" FORCE) + "${WARPCTC_INSTALL_DIR}/lib/warpctc.dll" CACHE FILEPATH "Warp-ctc Library" FORCE) ELSE(WIN32) + IF(APPLE) + SET(_warpctc_SHARED_SUFFIX dylib) + ELSE(APPLE) + SET(_warpctc_SHARED_SUFFIX so) + ENDIF(APPLE) + SET(WARPCTC_LIBRARIES - "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.a" CACHE FILEPATH "Warp-ctc Library" FORCE) + "${WARPCTC_INSTALL_DIR}/lib/libwarpctc.${_warpctc_SHARED_SUFFIX}" CACHE FILEPATH "Warp-ctc Library" FORCE) ENDIF(WIN32) IF(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" ) @@ -48,7 +54,7 @@ ExternalProject_Add( CMAKE_ARGS -DWITH_GPU=${WITH_GPU} CMAKE_ARGS -DWITH_OMP=${USE_OMP} CMAKE_ARGS -DWITH_TORCH=OFF - CMAKE_ARGS -DBUILD_SHARED=OFF + CMAKE_ARGS -DBUILD_SHARED=ON ) LIST(APPEND external_project_dependencies warpctc) diff --git a/paddle/gserver/tests/CMakeLists.txt b/paddle/gserver/tests/CMakeLists.txt index 286c66b996..0caa5e1e11 100644 --- a/paddle/gserver/tests/CMakeLists.txt +++ b/paddle/gserver/tests/CMakeLists.txt @@ -80,7 +80,7 @@ if(NOT WITH_DOUBLE) test_WarpCTCLayer.cpp) add_test(NAME test_WarpCTCLayer - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_WarpCTCLayer + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_WarpCTCLayer --warpctc_dir=${WARPCTC_LIB_DIR} WORKING_DIRECTORY ${PROJ_ROOT}/paddle) endif() From 4d6aca4b33de65615eb48b7c86070917b637ff22 Mon Sep 17 00:00:00 2001 From: liaogang Date: Mon, 9 Jan 2017 10:50:21 +0800 Subject: [PATCH 119/119] Warpctc only support dynamic load --- cmake/external/openblas.cmake | 1 + cmake/util.cmake | 4 ---- paddle/cuda/src/hl_warpctc_wrap.cc | 10 ---------- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/cmake/external/openblas.cmake b/cmake/external/openblas.cmake index 677999cc9f..66a72cd243 100644 --- a/cmake/external/openblas.cmake +++ b/cmake/external/openblas.cmake @@ -15,6 +15,7 @@ INCLUDE(cblas) IF(NOT ${CBLAS_FOUND}) + MESSAGE(FATAL_ERROR "Please install OpenBlas, MKL or ATLAS.") INCLUDE(ExternalProject) SET(CBLAS_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/openblas) diff --git a/cmake/util.cmake b/cmake/util.cmake index a6cb74853e..7da52bb758 100644 --- a/cmake/util.cmake +++ b/cmake/util.cmake @@ -121,10 +121,6 @@ function(link_paddle_exe TARGET_NAME) endif() endif() - if(NOT WITH_DSO) - target_link_libraries(${TARGET_NAME} ${WARPCTC_LIBRARIES}) - endif() - add_dependencies(${TARGET_NAME} ${external_project_dependencies}) endfunction() diff --git a/paddle/cuda/src/hl_warpctc_wrap.cc b/paddle/cuda/src/hl_warpctc_wrap.cc index 9ae8bc0f22..55b940ca67 100644 --- a/paddle/cuda/src/hl_warpctc_wrap.cc +++ b/paddle/cuda/src/hl_warpctc_wrap.cc @@ -29,7 +29,6 @@ void* warpctc_dso_handle = nullptr; * false, you need to add the path of libwarp-ctc.so to * the linked-libs of paddle or to LD_PRELOAD. */ -#ifdef PADDLE_USE_DSO #define DYNAMIC_LOAD_WARPCTC_WRAP(__name) \ struct DynLoad__##__name { \ template \ @@ -41,15 +40,6 @@ void* warpctc_dso_handle = nullptr; return reinterpret_cast(p_##_name)(args...); \ } \ } __name; // struct DynLoad__##__name -#else -#define DYNAMIC_LOAD_WARPCTC_WRAP(__name) \ - struct DynLoad__##__name { \ - template \ - auto operator()(Args... args) -> decltype(__name(args...)) { \ - return __name(args...); \ - } \ - } __name; // struct DynLoad__##__name -#endif // include all needed warp-ctc functions DYNAMIC_LOAD_WARPCTC_WRAP(get_warpctc_version)

3TkgP49@aJ{wQn#Z7- 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 diff --git a/doc/tutorials/gan/index_en.md b/doc/tutorials/gan/index_en.md index 99c8d73011..ac9ed37b22 100644 --- a/doc/tutorials/gan/index_en.md +++ b/doc/tutorials/gan/index_en.md @@ -4,9 +4,7 @@ This demo implements GAN training described in the original [GAN paper](https:// The high-level structure of GAN is shown in Figure. 1 below. It is composed of two major parts: a generator and a discriminator, both of which are based on neural networks. The generator takes in some kind of noise with a known distribution and transforms it into an image. The discriminator takes in an image and determines whether it is artificially generated by the generator or a real image. So the generator and the discriminator are in a competitive game in which generator is trying to generate image to look as real as possible to fool the discriminator, while the discriminator is trying to distinguish between real and fake images. -

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%0AYvh?PDi8XbWbg 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 literal 0 HcmV?d00001 diff --git a/doc/cluster/aws/efs_mount.png b/doc/cluster/aws/efs_mount.png new file mode 100644 index 0000000000000000000000000000000000000000..0f9e3cab98445707e5e9baa18ddabe15cdf04576 GIT binary patch literal 230609 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