chore: import upstream snapshot with attribution
This commit is contained in:
+237
@@ -0,0 +1,237 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: true
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertTrailingCommas: None
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: NextLine
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
- ParseTestProto
|
||||
- ParsePartialTestProto
|
||||
CanonicalDelimiter: pb
|
||||
BasedOnStyle: google
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
RequiresClausePosition: OwnLine
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
...
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: "\U0001F528Work Item (DEV ONLY)"
|
||||
about: Work item issue for tracking progress. Dev team only.
|
||||
title: ''
|
||||
labels: Work Item
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 🔨Work Item
|
||||
|
||||
**IMPORTANT:**
|
||||
* This template is only for dev team to track project progress. For feature request or bug report, please use the corresponding issue templates.
|
||||
* DO NOT create a new work item if the purpose is to fix an existing issue or feature request. We will directly use the issue in the project tracker.
|
||||
|
||||
Project tracker: https://github.com/orgs/dmlc/projects/2
|
||||
|
||||
## Description
|
||||
|
||||
<!-- short description of the work item -->
|
||||
|
||||
## Depending work items or issues
|
||||
|
||||
<!-- what must be done before this -->
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: "\U0001F41B Bug Report"
|
||||
about: Submit a bug report to help us improve DGL
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bug
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
## To Reproduce
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1.
|
||||
1.
|
||||
1.
|
||||
|
||||
<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->
|
||||
|
||||
## Expected behavior
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
## Environment
|
||||
|
||||
- DGL Version (e.g., 1.0):
|
||||
- Backend Library & Version (e.g., PyTorch 0.4.1, MXNet/Gluon 1.3):
|
||||
- OS (e.g., Linux):
|
||||
- How you installed DGL (`conda`, `pip`, source):
|
||||
- Build command you used (if compiling from source):
|
||||
- Python version:
|
||||
- CUDA/cuDNN version (if applicable):
|
||||
- GPU models and configuration (e.g. V100):
|
||||
- Any other relevant information:
|
||||
|
||||
## Additional context
|
||||
|
||||
<!-- Add any other context about the problem here. -->
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: "\U0001F4DA Documentation"
|
||||
about: Report an issue related to docs.dgl.ai
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
<!-- Please specify whether it's tutorial part or API reference part-->
|
||||
<!-- Describe the issue.-->
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: "\U0001F680Feature Request"
|
||||
about: Submit a proposal/request for a new DGL feature
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Feature
|
||||
<!-- A brief description of the feature proposal -->
|
||||
|
||||
## Motivation
|
||||
|
||||
<!-- Please outline the motivation for the proposal. Is your feature request
|
||||
related to a problem? e.g., I'm always frustrated when [...]. If this is
|
||||
related to another GitHub issue, please link here too -->
|
||||
|
||||
## Alternatives
|
||||
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered, if any. -->
|
||||
|
||||
## Pitch
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
## Additional context
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: "❓Questions/Help/Support"
|
||||
about: Do you need support? We have resources.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## ❓ Questions and Help
|
||||
|
||||
Before proceeding, please note that we recommend
|
||||
using our discussion forum (https://discuss.dgl.ai) for
|
||||
general questions. As a result, this issue will
|
||||
likely be CLOSED shortly.
|
||||
@@ -0,0 +1,20 @@
|
||||
## Description
|
||||
<!-- Brief description. Refer to the related issues if existed.
|
||||
It'll be great if relevant reviewers can be assigned as well.-->
|
||||
|
||||
## Checklist
|
||||
Please feel free to remove inapplicable items for your PR.
|
||||
- [ ] The PR title starts with [$CATEGORY] (such as [NN], [Model], [Doc], [Feature]])
|
||||
- [ ] I've leverage the [tools](https://docs.google.com/document/d/1iHyj7zlmygKSk5gBPsqIqL5ASPzJSPREaNT_QdsiYA4/edit) to beautify the python and c++ code.
|
||||
- [ ] The PR is complete and small, read the [Google eng practice (CL equals to PR)](https://google.github.io/eng-practices/review/developer/small-cls.html) to understand more about small PR. In DGL, we consider PRs with less than 200 lines of core code change are small (example, test and documentation could be exempted).
|
||||
- [ ] All changes have test coverage
|
||||
- [ ] Code is well-documented
|
||||
- [ ] To the best of my knowledge, examples are either not affected by this change, or have been fixed to be compatible with this change
|
||||
- [ ] Related issue is referred in this PR
|
||||
- [ ] If the PR is for a new model/paper, I've updated the example index [here](../examples/README.md).
|
||||
|
||||
## Changes
|
||||
<!-- You could use following template
|
||||
- [ ] Feature1, tests, (and when applicable, API doc)
|
||||
- [ ] Feature2, tests, (and when applicable, API doc)
|
||||
-->
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Lint
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
lintrunner:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Pull DGL
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout master and HEAD
|
||||
run: |
|
||||
git checkout -t origin/master
|
||||
git checkout ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.8'
|
||||
|
||||
- name: Install requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install lintrunner --user
|
||||
|
||||
- name: Initialize lint dependencies
|
||||
run: lintrunner init
|
||||
|
||||
- name: Run lintrunner on all changed files
|
||||
run: |
|
||||
set +e
|
||||
if ! lintrunner --force-color -m master --tee-json=lint.json; then
|
||||
echo ""
|
||||
echo -e "\e[1m\e[36mYou can reproduce these results locally by using \`lintrunner\`.\e[0m"
|
||||
echo -e "\e[1m\e[36mSee https://github.com/pytorch/pytorch/wiki/lintrunner for setup instructions.\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Store annotations
|
||||
if: always() && github.event_name == 'pull_request'
|
||||
# Don't show this as an error; the above step will have already failed.
|
||||
continue-on-error: true
|
||||
run: |
|
||||
# Use jq to massage the JSON lint output into GitHub Actions workflow commands.
|
||||
jq --raw-output \
|
||||
'"::\(if .severity == "advice" or .severity == "disabled" then "warning" else .severity end) file=\(.path),line=\(.line),col=\(.char),title=\(.code) \(.name)::" + (.description | gsub("\\n"; "%0A"))' \
|
||||
lint.json
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name == 'workflow_dispatch' }}
|
||||
cancel-in-progress: true
|
||||
@@ -0,0 +1,32 @@
|
||||
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
||||
#
|
||||
# You can adjust the behavior by modifying this file.
|
||||
# For more information, see:
|
||||
# https://github.com/actions/stale
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v4.1.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: -1 # disable issue close
|
||||
days-before-pr-stale: -1 # disable stale bot on pr
|
||||
days-before-pr-close: -1 # disable stale bot on pr
|
||||
stale-issue-message: 'This issue has been automatically marked as stale due to lack of activity. It will be closed if no further activity occurs. Thank you'
|
||||
close-issue-message: 'This issue is closed due to lack of activity. Feel free to reopen it if you still have questions.'
|
||||
stale-issue-label: 'stale-issue'
|
||||
exempt-issue-labels: 'bug:confirmed,feature request,help wanted,Work Item'
|
||||
exempt-all-issue-milestones: true
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
# IDE
|
||||
.idea
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
dataset/
|
||||
datasets/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Whitelist some distribution / package non-related directories
|
||||
!tests/dist
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
examples/pytorch/data/ind.pubmed.y
|
||||
examples/pytorch/data/ind.pubmed.x
|
||||
examples/pytorch/data/ind.pubmed.ty
|
||||
examples/pytorch/data/ind.pubmed.tx
|
||||
examples/pytorch/data/ind.pubmed.test.index
|
||||
examples/pytorch/data/ind.pubmed.graph
|
||||
examples/pytorch/data/ind.pubmed.ally
|
||||
examples/pytorch/data/ind.pubmed.allx
|
||||
examples/pytorch/data/ind.cora.y
|
||||
examples/pytorch/data/ind.cora.x
|
||||
examples/pytorch/data/ind.cora.ty
|
||||
examples/pytorch/data/ind.cora.tx
|
||||
examples/pytorch/data/ind.cora.test.index
|
||||
examples/pytorch/data/ind.cora.graph
|
||||
examples/pytorch/data/ind.cora.ally
|
||||
examples/pytorch/data/ind.cora.allx
|
||||
examples/pytorch/data/ind.citeseer.y
|
||||
examples/pytorch/data/ind.citeseer.x
|
||||
examples/pytorch/data/ind.citeseer.ty
|
||||
examples/pytorch/data/ind.citeseer.tx
|
||||
examples/pytorch/data/ind.citeseer.test.index
|
||||
examples/pytorch/data/ind.citeseer.graph
|
||||
examples/pytorch/data/ind.citeseer.ally
|
||||
examples/pytorch/data/ind.citeseer.allx
|
||||
examples/pytorch/.DS_Store
|
||||
examples/.DS_Store
|
||||
examples/pytorch/generative_graph/*.p
|
||||
.DS_Store
|
||||
|
||||
# data directory
|
||||
_download
|
||||
|
||||
# CTags & CScope
|
||||
tags
|
||||
cscope.*
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
*.swo
|
||||
*.un~
|
||||
*~
|
||||
|
||||
# parameters
|
||||
*.params
|
||||
|
||||
# vscode
|
||||
.clangd
|
||||
.vscode
|
||||
|
||||
# asv
|
||||
.asv
|
||||
|
||||
.ycm_extra_conf.py
|
||||
**.png
|
||||
|
||||
# model file
|
||||
*.pth
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
[submodule "third_party/dmlc-core"]
|
||||
path = third_party/dmlc-core
|
||||
url = https://github.com/dmlc/dmlc-core.git
|
||||
[submodule "third_party/dlpack"]
|
||||
path = third_party/dlpack
|
||||
url = https://github.com/dmlc/dlpack.git
|
||||
[submodule "third_party/googletest"]
|
||||
path = third_party/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
[submodule "third_party/METIS"]
|
||||
path = third_party/METIS
|
||||
url = https://github.com/KarypisLab/METIS.git
|
||||
[submodule "third_party/nanoflann"]
|
||||
path = third_party/nanoflann
|
||||
url = https://github.com/jlblancoc/nanoflann
|
||||
[submodule "third_party/libxsmm"]
|
||||
path = third_party/libxsmm
|
||||
url = https://github.com/hfp/libxsmm.git
|
||||
[submodule "third_party/pcg"]
|
||||
path = third_party/pcg
|
||||
url = https://github.com/imneme/pcg-cpp.git
|
||||
[submodule "third_party/cccl"]
|
||||
path = third_party/cccl
|
||||
url = https://github.com/NVIDIA/cccl.git
|
||||
[submodule "third_party/liburing"]
|
||||
path = third_party/liburing
|
||||
url = https://github.com/axboe/liburing.git
|
||||
[submodule "third_party/cuco"]
|
||||
path = third_party/cuco
|
||||
url = https://github.com/NVIDIA/cuCollections.git
|
||||
[submodule "third_party/GKlib"]
|
||||
path = third_party/GKlib
|
||||
url = https://github.com/KarypisLab/GKlib.git
|
||||
[submodule "third_party/taskflow"]
|
||||
path = third_party/taskflow
|
||||
url = https://github.com/taskflow/taskflow.git
|
||||
[submodule "third_party/tsl_robin_map"]
|
||||
path = third_party/tsl_robin_map
|
||||
url = https://github.com/Tessil/robin-map.git
|
||||
@@ -0,0 +1,57 @@
|
||||
# Black + usort
|
||||
[[linter]]
|
||||
code = 'UFMT'
|
||||
include_patterns = [
|
||||
'**/*.py',
|
||||
]
|
||||
command = [
|
||||
'python3',
|
||||
'tests/lint/ufmt_linter.py',
|
||||
'--',
|
||||
'@{{PATHSFILE}}'
|
||||
]
|
||||
exclude_patterns = [
|
||||
'.github/*',
|
||||
'build/*',
|
||||
'cmake/*',
|
||||
'conda/*',
|
||||
'docker/*',
|
||||
'third_party/*',
|
||||
]
|
||||
init_command = [
|
||||
'python3',
|
||||
'tests/lint/pip_init.py',
|
||||
'--dry-run={{DRYRUN}}',
|
||||
'black==22.10.0',
|
||||
'ufmt==2.0.1',
|
||||
'usort==1.0.5',
|
||||
]
|
||||
is_formatter = true
|
||||
|
||||
[[linter]]
|
||||
code = 'CLANGFORMAT'
|
||||
include_patterns = [
|
||||
'**/*.h',
|
||||
'**/*.c',
|
||||
'**/*.cc',
|
||||
'**/*.cpp',
|
||||
'**/*.cuh',
|
||||
'**/*.cu',
|
||||
]
|
||||
exclude_patterns = [
|
||||
'third_party/**',
|
||||
]
|
||||
init_command = [
|
||||
'python3',
|
||||
'tests/lint/pip_init.py',
|
||||
'--dry-run={{DRYRUN}}',
|
||||
'clang-format==15.0.4',
|
||||
]
|
||||
command = [
|
||||
'python3',
|
||||
'tests/lint/clangformat_linter.py',
|
||||
'--binary=clang-format',
|
||||
'--',
|
||||
'@{{PATHSFILE}}'
|
||||
]
|
||||
is_formatter = true
|
||||
+550
@@ -0,0 +1,550 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
########################################
|
||||
# Borrowed and adapted from TVM project
|
||||
########################################
|
||||
project(dgl C CXX)
|
||||
message(STATUS "Start configuring project ${PROJECT_NAME}")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# cmake utils
|
||||
include(cmake/util/Util.cmake)
|
||||
include(cmake/util/MshadowUtil.cmake)
|
||||
include(cmake/util/FindCUDA.cmake)
|
||||
|
||||
# Options for building DGL.
|
||||
# NOTE: Please avoid editing this file to change build type. Instead, using
|
||||
# bash script/build_dgl.sh -e -t release to overwrite the value.
|
||||
dgl_option(BUILD_TYPE "Type of the build: dev, dogfood or release" "dev")
|
||||
message(STATUS "Build for ${BUILD_TYPE}")
|
||||
|
||||
dgl_option(USE_CUDA "Build with CUDA" OFF)
|
||||
dgl_option(TORCH_PYTHON_INTERPS "Python interpreter for building sub-components" python3)
|
||||
|
||||
# Conda build related options.
|
||||
dgl_option(EXTERNAL_DLPACK_PATH "Path to external dlpack" OFF)
|
||||
dgl_option(EXTERNAL_DMLC_PATH "Path to external dmlc-core" OFF)
|
||||
dgl_option(EXTERNAL_DMLC_LIB_PATH "Path to external dmlc-core library" OFF)
|
||||
dgl_option(EXTERNAL_PHMAP_PATH "Path to external parallel-hashmap" OFF)
|
||||
dgl_option(EXTERNAL_NANOFLANN_PATH "Path to use external nanoflann" OFF)
|
||||
dgl_option(EXTERNAL_METIS_PATH "Path to external metis" OFF)
|
||||
dgl_option(EXTERNAL_METIS_LIB_PATH "Path to external metis library" OFF)
|
||||
dgl_option(EXTERNAL_GKLIB_PATH "Path to external gklib" OFF)
|
||||
|
||||
# Options for building DGL features: "none," "dev," "dogfood," "release," and
|
||||
# "all."
|
||||
# "none" - The feature is OFF for all build types. This is used when
|
||||
# disabling a feature.
|
||||
# "dev" - The feature is ON for dev build. The default build from source
|
||||
# and the build for unit tests are using this build type.
|
||||
# "dogfood" - The major function of this feature is done. The regression and
|
||||
# benchmark framework are using this build type.
|
||||
# "release" - The feature will be build for release.
|
||||
# "all" - The feature is ON for all build types. Equivalent to set ["dev"
|
||||
# "dogfood" "release"].
|
||||
# NOTE: Please avoid editing this file to change feature options for a local
|
||||
# build. Instead, using bash script/build_dgl.sh -e '-DFEATURE_NAME=ON/OFF' to
|
||||
# overwrite the value.
|
||||
dgl_feature_option(
|
||||
BUILD_SPARSE
|
||||
"Build DGL sparse library"
|
||||
"all"
|
||||
)
|
||||
dgl_feature_option(
|
||||
BUILD_TORCH
|
||||
"Build the PyTorch plugin"
|
||||
"all"
|
||||
)
|
||||
dgl_feature_option(
|
||||
USE_EPOLL
|
||||
"Build with epoll for socket communicator"
|
||||
"all"
|
||||
)
|
||||
dgl_feature_option(
|
||||
USE_LIBXSMM
|
||||
"Build with LIBXSMM library optimization"
|
||||
"all"
|
||||
)
|
||||
dgl_feature_option(
|
||||
USE_OPENMP
|
||||
"Build with OpenMP"
|
||||
"all"
|
||||
)
|
||||
|
||||
dgl_feature_option(
|
||||
BUILD_GRAPHBOLT
|
||||
"Build Graphbolt library"
|
||||
"all"
|
||||
)
|
||||
|
||||
dgl_feature_option(
|
||||
LIBCXX_ENABLE_PARALLEL_ALGORITHMS
|
||||
"Enable the parallel algorithms library. This requires the PSTL to be available."
|
||||
"none"
|
||||
)
|
||||
dgl_feature_option(
|
||||
REBUILD_LIBXSMM
|
||||
"Clean LIBXSMM build cache at every build"
|
||||
"none"
|
||||
)
|
||||
dgl_feature_option(
|
||||
USE_HDFS
|
||||
"Build with HDFS support"
|
||||
"none"
|
||||
) # Set env HADOOP_HDFS_HOME if needed
|
||||
dgl_feature_option(
|
||||
USE_S3
|
||||
"Build with S3 support"
|
||||
"none"
|
||||
)
|
||||
|
||||
# Only build C++ tests for unit testing purposes in dev build.
|
||||
dgl_feature_option(
|
||||
BUILD_CPP_TEST
|
||||
"Build cpp unittest executables"
|
||||
"dev"
|
||||
)
|
||||
|
||||
if (EXTERNAL_DLPACK_PATH OR EXTERNAL_DMLC_PATH OR EXTERNAL_NANOFLANN_PATH OR EXTERNAL_NANOFLANN_PATH OR EXTERNAL_METIS_PATH OR EXTERNAL_GKLIB_PATH)
|
||||
message(STATUS "Using at least one external library")
|
||||
set(USE_EXTERNAL_LIBS ON)
|
||||
|
||||
if (BUILD_CPP_TEST)
|
||||
message(FATAL_ERROR "Cannot build cpp unittests with external libraries")
|
||||
endif(BUILD_CPP_TEST)
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
||||
|
||||
# Set optimization options for different build types.
|
||||
if (${BUILD_TYPE} STREQUAL "dev")
|
||||
if (MSVC)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Od")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Od")
|
||||
else()
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g3 -ggdb")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g3 -ggdb")
|
||||
endif()
|
||||
else()
|
||||
if (MSVC)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /O2 /DNDEBUG")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2 /DNDEBUG")
|
||||
else()
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -DNDEBUG")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -DNDEBUG")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(USE_CUDA)
|
||||
message(STATUS "Build with CUDA support")
|
||||
project(dgl C CXX)
|
||||
include(cmake/modules/CUDA.cmake)
|
||||
message(STATUS "Use external CCCL library for a consistent API and performance.")
|
||||
cuda_include_directories(BEFORE "${CMAKE_SOURCE_DIR}/third_party/cccl/thrust")
|
||||
cuda_include_directories(BEFORE "${CMAKE_SOURCE_DIR}/third_party/cccl/cub")
|
||||
cuda_include_directories(BEFORE "${CMAKE_SOURCE_DIR}/third_party/cccl/libcudacxx/include")
|
||||
endif(USE_CUDA)
|
||||
|
||||
# initial variables
|
||||
if(NOT MSVC)
|
||||
set(DGL_LINKER_LIBS "dl")
|
||||
endif(NOT MSVC)
|
||||
|
||||
if(MSVC OR CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(DGL_RUNTIME_LINKER_LIBS "")
|
||||
else(MSVC OR CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(DGL_RUNTIME_LINKER_LIBS "rt")
|
||||
endif(MSVC OR CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
|
||||
# Generic compilation options
|
||||
if(MSVC)
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
|
||||
add_definitions(-DNOMINMAX)
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /bigobj")
|
||||
if(USE_MSVC_MT)
|
||||
foreach(flag_var
|
||||
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
||||
if(${flag_var} MATCHES "/MD")
|
||||
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
|
||||
endif(${flag_var} MATCHES "/MD")
|
||||
endforeach(flag_var)
|
||||
endif()
|
||||
else(MSVC)
|
||||
include(CheckCXXCompilerFlag)
|
||||
set(CMAKE_C_FLAGS "-Wall -fPIC ${CMAKE_C_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "-Wall -fPIC ${CMAKE_CXX_FLAGS}")
|
||||
if(NOT APPLE)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--warn-common ${CMAKE_SHARED_LINKER_FLAGS}")
|
||||
endif(NOT APPLE)
|
||||
endif(MSVC)
|
||||
|
||||
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
|
||||
message(STATUS "Disabling LIBXSMM on ${CMAKE_SYSTEM_PROCESSOR}.")
|
||||
set(USE_LIBXSMM OFF)
|
||||
endif()
|
||||
|
||||
# Source file lists
|
||||
file(GLOB DGL_SRC
|
||||
src/*.cc
|
||||
src/array/*.cc
|
||||
src/array/cpu/*.cc
|
||||
src/random/*.cc
|
||||
src/random/cpu/*.cc
|
||||
src/runtime/*.cc
|
||||
src/geometry/*.cc
|
||||
src/geometry/cpu/*.cc
|
||||
src/partition/*.cc
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE DGL_SRC_1
|
||||
src/api/*.cc
|
||||
src/graph/*.cc
|
||||
src/scheduler/*.cc
|
||||
)
|
||||
|
||||
list(APPEND DGL_SRC ${DGL_SRC_1})
|
||||
|
||||
if (NOT MSVC)
|
||||
file(GLOB_RECURSE DGL_RPC_SRC src/rpc/*.cc)
|
||||
else()
|
||||
file(GLOB_RECURSE DGL_RPC_SRC src/rpc/network/*.cc)
|
||||
endif()
|
||||
list(APPEND DGL_SRC ${DGL_RPC_SRC})
|
||||
|
||||
if(USE_OPENMP)
|
||||
find_package(OpenMP REQUIRED)
|
||||
list(APPEND DGL_LINKER_LIBS OpenMP::OpenMP_CXX)
|
||||
message(STATUS "Build with OpenMP.")
|
||||
endif(USE_OPENMP)
|
||||
|
||||
# Configure cuda
|
||||
if(USE_CUDA)
|
||||
file(GLOB_RECURSE DGL_CUDA_SRC
|
||||
src/array/cuda/*.cc
|
||||
src/array/cuda/*.cu
|
||||
src/array/cuda/uvm/*.cc
|
||||
src/array/cuda/uvm/*.cu
|
||||
src/kernel/cuda/*.cc
|
||||
src/kernel/cuda/*.cu
|
||||
src/partition/cuda/*.cu
|
||||
src/runtime/cuda/*.cc
|
||||
src/runtime/cuda/*.cu
|
||||
src/geometry/cuda/*.cu
|
||||
src/graph/transform/cuda/*.cu
|
||||
src/graph/sampling/randomwalks/*.cu
|
||||
)
|
||||
list(APPEND DGL_SRC ${DGL_CUDA_SRC})
|
||||
dgl_config_cuda(DGL_LINKER_LIBS)
|
||||
cuda_add_library(dgl SHARED ${DGL_SRC})
|
||||
else(USE_CUDA)
|
||||
add_library(dgl SHARED ${DGL_SRC})
|
||||
endif(USE_CUDA)
|
||||
|
||||
if ((NOT MSVC) AND USE_EPOLL)
|
||||
INCLUDE(CheckIncludeFile)
|
||||
check_include_file("sys/epoll.h" EPOLL_AVAILABLE)
|
||||
if (EPOLL_AVAILABLE)
|
||||
target_compile_definitions(dgl PRIVATE USE_EPOLL)
|
||||
else()
|
||||
message(WARNING "EPOLL is not available on this platform...")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
# include directories
|
||||
target_include_directories(dgl PRIVATE "include")
|
||||
# check for conda includes
|
||||
if("$ENV{CONDA_BUILD}" STREQUAL "1")
|
||||
set(in_conda_build TRUE)
|
||||
message(STATUS "Conda build environment detected")
|
||||
elseif(DEFINED ENV{CONDA_PREFIX})
|
||||
set(in_conda_prefix TRUE)
|
||||
message(STATUS "Conda environment detected: $ENV{CONDA_PREFIX}")
|
||||
endif()
|
||||
|
||||
if (USE_CONDA_INCLUDES)
|
||||
if(in_conda_build)
|
||||
message(STATUS "Using Conda build environment includes: $ENV{PREFIX}")
|
||||
target_include_directories(dgl PRIVATE "$ENV{PREFIX}/include" "$ENV{BUILD_PREFIX}/include")
|
||||
elseif(in_conda_prefix)
|
||||
message(STATUS "Using Conda environment includes: $ENV{CONDA_PREFIX}")
|
||||
target_include_directories(dgl PRIVATE "$ENV{CONDA_PREFIX}/include")
|
||||
else()
|
||||
message(FATAL_ERROR "Conda environment not detected")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(EXTERNAL_DLPACK_PATH)
|
||||
message(STATUS "looking for dlpack headers in ${EXTERNAL_DLPACK_PATH}")
|
||||
include_directories(SYSTEM ${EXTERNAL_DLPACK_PATH})
|
||||
else(EXTERNAL_DLPACK_PATH)
|
||||
target_include_directories(dgl PRIVATE "third_party/dlpack/include")
|
||||
endif(EXTERNAL_DLPACK_PATH)
|
||||
|
||||
if(EXTERNAL_DMLC_PATH)
|
||||
if (USE_HDFS)
|
||||
message(FATAL_ERROR "Cannot use HDFS and external dmlc-core at the same time")
|
||||
endif()
|
||||
message(STATUS "looking for dmlc headers in ${EXTERNAL_DMLC_PATH}")
|
||||
include_directories(SYSTEM ${EXTERNAL_DMLC_PATH})
|
||||
|
||||
if (NOT EXTERNAL_DMLC_LIB_PATH)
|
||||
message(FATAL_ERROR "EXTERNAL_DMLC_LIB_PATH must be set if EXTERNAL_DMLC_PATH is set")
|
||||
endif()
|
||||
message(STATUS "looking for dmlc library in ${EXTERNAL_DMLC_LIB_PATH}")
|
||||
find_package(dmlc
|
||||
REQUIRED
|
||||
HINTS ${EXTERNAL_DMLC_LIB_PATH}
|
||||
)
|
||||
if(NOT dmlc_FOUND)
|
||||
message(FATAL_ERROR "Failed to find DMLC library")
|
||||
endif()
|
||||
list(APPEND DGL_LINKER_LIBS dmlc::dmlc)
|
||||
|
||||
else(EXTERNAL_DMLC_PATH)
|
||||
target_include_directories(dgl PRIVATE "third_party/dmlc-core/include")
|
||||
# For serialization
|
||||
if (USE_HDFS)
|
||||
option(DMLC_HDFS_SHARED "dgl has to build with dynamic hdfs library" ON)
|
||||
endif()
|
||||
add_subdirectory("third_party/dmlc-core")
|
||||
list(APPEND DGL_LINKER_LIBS dmlc)
|
||||
set(GOOGLE_TEST 0) # Turn off dmlc-core test
|
||||
endif(EXTERNAL_DMLC_PATH)
|
||||
|
||||
target_include_directories(dgl PRIVATE "tensoradapter/include")
|
||||
target_include_directories(dgl PRIVATE "third_party/pcg/include")
|
||||
target_include_directories(dgl PRIVATE "third_party/tsl_robin_map/include")
|
||||
|
||||
if(EXTERNAL_NANOFLANN_PATH)
|
||||
include_directories(SYSTEM ${EXTERNAL_NANOFLANN_PATH})
|
||||
else(EXTERNAL_NANOFLANN_PATH)
|
||||
target_include_directories(dgl PRIVATE "third_party/nanoflann/include")
|
||||
endif(EXTERNAL_NANOFLANN_PATH)
|
||||
|
||||
if (USE_LIBXSMM)
|
||||
target_compile_definitions(dgl PRIVATE USE_LIBXSMM DGL_CPU_LLC_SIZE=40000000 __BLAS=0)
|
||||
target_include_directories(dgl PRIVATE "third_party/libxsmm/include")
|
||||
message(STATUS "Build with LIBXSMM optimization.")
|
||||
endif()
|
||||
|
||||
# To compile METIS correct for DGL.
|
||||
add_compile_definitions(IDXTYPEWIDTH=64 REALTYPEWIDTH=32)
|
||||
if (EXTERNAL_METIS_PATH)
|
||||
# To compile METIS correct for DGL.
|
||||
if(MSVC)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /DIDXTYPEWIDTH=64 /DREALTYPEWIDTH=32")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DIDXTYPEWIDTH=64 /DREALTYPEWIDTH=32")
|
||||
else(MSVC)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DIDXTYPEWIDTH=64 -DREALTYPEWIDTH=32")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DIDXTYPEWIDTH=64 -DREALTYPEWIDTH=32")
|
||||
endif(MSVC)
|
||||
find_package(METIS REQUIRED)
|
||||
message(STATUS "Found METIS library")
|
||||
target_include_directories(dgl SYSTEM PUBLIC ${METIS_INCLUDE_DIR})
|
||||
list(APPEND DGL_LINKER_LIBS ${METIS_LIBRARIES})
|
||||
else(EXTERNAL_METIS_PATH)
|
||||
target_include_directories(dgl PRIVATE "third_party/METIS/include")
|
||||
# Compile METIS
|
||||
if(NOT MSVC)
|
||||
set(GKLIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/third_party/GKlib")
|
||||
include(${GKLIB_PATH}/GKlibSystem.cmake)
|
||||
include_directories(${GKLIB_PATH})
|
||||
add_library(GKlib ${GKlib_sources})
|
||||
include_directories("third_party/METIS/include/")
|
||||
add_subdirectory("third_party/METIS/libmetis/")
|
||||
# When building on ubi7, it fails with the following error:
|
||||
# /usr/include/signal.h:156:29: error: unknown type name 'siginfo_t'.
|
||||
# So I(Rui) define _POSIX_C_SOURCE to 200809L for GKlib and metis to avoid the error.
|
||||
target_compile_definitions(GKlib PRIVATE _POSIX_C_SOURCE=200809L)
|
||||
target_compile_definitions(metis PRIVATE _POSIX_C_SOURCE=200809L)
|
||||
list(APPEND DGL_LINKER_LIBS metis GKlib)
|
||||
endif(NOT MSVC)
|
||||
endif(EXTERNAL_METIS_PATH)
|
||||
|
||||
|
||||
# Avoid exposing third-party symbols when using DGL as a library.
|
||||
if((NOT MSVC) AND (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin"))
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,--exclude-libs,ALL")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--exclude-libs,ALL")
|
||||
endif()
|
||||
|
||||
# Compile gpu_cache
|
||||
if(USE_CUDA)
|
||||
# Manually build gpu_cache because CMake always builds it as shared
|
||||
file(GLOB gpu_cache_src
|
||||
third_party/HugeCTR/gpu_cache/src/nv_gpu_cache.cu
|
||||
)
|
||||
cuda_add_library(gpu_cache STATIC ${gpu_cache_src})
|
||||
target_include_directories(gpu_cache PRIVATE "third_party/HugeCTR/gpu_cache/include")
|
||||
target_include_directories(dgl PRIVATE "third_party/HugeCTR/gpu_cache/include")
|
||||
list(APPEND DGL_LINKER_LIBS gpu_cache)
|
||||
message(STATUS "Build with HugeCTR GPU embedding cache.")
|
||||
endif(USE_CUDA)
|
||||
|
||||
# support PARALLEL_ALGORITHMS
|
||||
if (LIBCXX_ENABLE_PARALLEL_ALGORITHMS)
|
||||
target_compile_definitions(dgl PRIVATE PARALLEL_ALGORITHMS)
|
||||
endif(LIBCXX_ENABLE_PARALLEL_ALGORITHMS)
|
||||
|
||||
target_link_libraries(dgl ${DGL_LINKER_LIBS} ${DGL_RUNTIME_LINKER_LIBS})
|
||||
if(MSVC)
|
||||
add_custom_command(
|
||||
TARGET dgl POST_BUILD COMMAND
|
||||
${CMAKE_COMMAND} -E copy "$<TARGET_FILE:dgl>" "$<TARGET_FILE_DIR:dgl>/..")
|
||||
endif(MSVC)
|
||||
|
||||
# Tensor adapter libraries
|
||||
# Linking against LibTorch involves linking against a bunch of other libraries
|
||||
# returned by PyTorch's CMake (e.g. C10 or NVTools). Because CMake caches
|
||||
# the found libraries in find_library(), often times CMake will look into the libraries
|
||||
# of the wrong version when I build everything in the same CMake process. As
|
||||
# a result, I (BarclayII) am launching an individual CMake build for every PyTorch version.
|
||||
if(BUILD_TORCH)
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR} BINDIR)
|
||||
file(TO_NATIVE_PATH ${CMAKE_COMMAND} CMAKE_CMD)
|
||||
if(MSVC)
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/tensoradapter/pytorch/build.bat BUILD_SCRIPT)
|
||||
add_custom_target(
|
||||
tensoradapter_pytorch
|
||||
${CMAKE_COMMAND} -E env
|
||||
CMAKE_COMMAND=${CMAKE_CMD}
|
||||
CUDA_TOOLKIT_ROOT_DIR=${CUDA_TOOLKIT_ROOT_DIR}
|
||||
USE_CUDA=${USE_CUDA}
|
||||
EXTERNAL_DMLC_LIB_PATH=${EXTERNAL_DMLC_LIB_PATH}
|
||||
BINDIR=${BINDIR}
|
||||
cmd /e:on /c ${BUILD_SCRIPT} ${TORCH_PYTHON_INTERPS}
|
||||
DEPENDS ${BUILD_SCRIPT}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tensoradapter/pytorch)
|
||||
else(MSVC)
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/tensoradapter/pytorch/build.sh BUILD_SCRIPT)
|
||||
add_custom_target(
|
||||
tensoradapter_pytorch
|
||||
${CMAKE_COMMAND} -E env
|
||||
CMAKE_COMMAND=${CMAKE_CMD}
|
||||
CUDA_TOOLKIT_ROOT_DIR=${CUDA_TOOLKIT_ROOT_DIR}
|
||||
USE_CUDA=${USE_CUDA}
|
||||
EXTERNAL_DMLC_LIB_PATH=${EXTERNAL_DMLC_LIB_PATH}
|
||||
BINDIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
bash ${BUILD_SCRIPT} ${TORCH_PYTHON_INTERPS}
|
||||
DEPENDS ${BUILD_SCRIPT}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tensoradapter/pytorch)
|
||||
endif(MSVC)
|
||||
add_dependencies(dgl tensoradapter_pytorch)
|
||||
endif(BUILD_TORCH)
|
||||
|
||||
# Installation rules
|
||||
install(TARGETS dgl DESTINATION lib${LIB_SUFFIX})
|
||||
|
||||
# Testing
|
||||
if(BUILD_CPP_TEST)
|
||||
message(STATUS "Build with unittest")
|
||||
add_subdirectory(./third_party/googletest)
|
||||
enable_testing()
|
||||
include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
|
||||
include_directories("include")
|
||||
include_directories("third_party/dlpack/include")
|
||||
include_directories("third_party/dmlc-core/include")
|
||||
include_directories("third_party/tsl_robin_map/include")
|
||||
include_directories("third_party/libxsmm/include")
|
||||
include_directories("third_party/pcg/include")
|
||||
file(GLOB_RECURSE TEST_SRC_FILES ${PROJECT_SOURCE_DIR}/tests/cpp/*.cc)
|
||||
add_executable(runUnitTests ${TEST_SRC_FILES})
|
||||
target_link_libraries(runUnitTests gtest gtest_main)
|
||||
target_link_libraries(runUnitTests dgl)
|
||||
add_test(UnitTests runUnitTests)
|
||||
endif(BUILD_CPP_TEST)
|
||||
|
||||
if(BUILD_SPARSE)
|
||||
message(STATUS "Configuring DGL sparse library")
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR} BINDIR)
|
||||
file(TO_NATIVE_PATH ${CMAKE_COMMAND} CMAKE_CMD)
|
||||
get_target_property(DGL_INCLUDE_DIRS dgl INCLUDE_DIRECTORIES)
|
||||
message(STATUS "DGL include directories: ${DGL_INCLUDE_DIRS}")
|
||||
message(STATUS "DGL link directories: ${DGL_INCLUDE_DIRS}")
|
||||
if(MSVC)
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dgl_sparse/build.bat BUILD_SCRIPT)
|
||||
add_custom_target(
|
||||
dgl_sparse
|
||||
ALL
|
||||
${CMAKE_COMMAND} -E env
|
||||
CMAKE_COMMAND=${CMAKE_CMD}
|
||||
CUDA_TOOLKIT_ROOT_DIR=${CUDA_TOOLKIT_ROOT_DIR}
|
||||
USE_CUDA=${USE_CUDA}
|
||||
BINDIR=${BINDIR}
|
||||
INCLUDEDIR="${DGL_INCLUDE_DIRS}"
|
||||
CFLAGS=${CMAKE_C_FLAGS}
|
||||
CXXFLAGS=${CMAKE_CXX_FLAGS}
|
||||
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}
|
||||
cmd /e:on /c ${BUILD_SCRIPT} ${TORCH_PYTHON_INTERPS}
|
||||
DEPENDS ${BUILD_SCRIPT}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dgl_sparse)
|
||||
else(MSVC)
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dgl_sparse/build.sh BUILD_SCRIPT)
|
||||
add_custom_target(
|
||||
dgl_sparse
|
||||
ALL
|
||||
${CMAKE_COMMAND} -E env
|
||||
CMAKE_COMMAND=${CMAKE_CMD}
|
||||
CUDA_TOOLKIT_ROOT_DIR=${CUDA_TOOLKIT_ROOT_DIR}
|
||||
USE_CUDA=${USE_CUDA}
|
||||
BINDIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
INCLUDEDIR="${DGL_INCLUDE_DIRS}"
|
||||
CFLAGS=${CMAKE_C_FLAGS}
|
||||
CXXFLAGS=${CMAKE_CXX_FLAGS}
|
||||
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}
|
||||
bash ${BUILD_SCRIPT} ${TORCH_PYTHON_INTERPS}
|
||||
DEPENDS ${BUILD_SCRIPT}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dgl_sparse)
|
||||
endif(MSVC)
|
||||
add_dependencies(dgl_sparse dgl)
|
||||
endif(BUILD_SPARSE)
|
||||
|
||||
if(BUILD_GRAPHBOLT)
|
||||
message(STATUS "Configuring graphbolt library")
|
||||
string(REPLACE ";" "\\;" CUDA_ARCHITECTURES_ESCAPED "${CUDA_ARCHITECTURES}")
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR} BINDIR)
|
||||
file(TO_NATIVE_PATH ${CMAKE_COMMAND} CMAKE_CMD)
|
||||
if(MSVC)
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/graphbolt/build.bat BUILD_SCRIPT)
|
||||
add_custom_target(
|
||||
graphbolt
|
||||
ALL
|
||||
${CMAKE_COMMAND} -E env
|
||||
CMAKE_COMMAND=${CMAKE_CMD}
|
||||
CUDA_TOOLKIT_ROOT_DIR=${CUDA_TOOLKIT_ROOT_DIR}
|
||||
USE_CUDA=${USE_CUDA}
|
||||
BINDIR=${BINDIR}
|
||||
CFLAGS=${CMAKE_C_FLAGS}
|
||||
CXXFLAGS=${CMAKE_CXX_FLAGS}
|
||||
CUDAARCHS="${CUDA_ARCHITECTURES_ESCAPED}"
|
||||
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}
|
||||
cmd /e:on /c ${BUILD_SCRIPT} ${TORCH_PYTHON_INTERPS}
|
||||
DEPENDS ${BUILD_SCRIPT}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/graphbolt)
|
||||
else(MSVC)
|
||||
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/graphbolt/build.sh BUILD_SCRIPT)
|
||||
add_custom_target(
|
||||
graphbolt
|
||||
ALL
|
||||
${CMAKE_COMMAND} -E env
|
||||
CMAKE_COMMAND=${CMAKE_CMD}
|
||||
CUDA_TOOLKIT_ROOT_DIR=${CUDA_TOOLKIT_ROOT_DIR}
|
||||
USE_CUDA=${USE_CUDA}
|
||||
USE_LIBURING=${USE_LIBURING}
|
||||
BINDIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
CFLAGS=${CMAKE_C_FLAGS}
|
||||
CXXFLAGS=${CMAKE_CXX_FLAGS}
|
||||
CUDAARCHS="${CUDA_ARCHITECTURES_ESCAPED}"
|
||||
LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}
|
||||
bash ${BUILD_SCRIPT} ${TORCH_PYTHON_INTERPS}
|
||||
DEPENDS ${BUILD_SCRIPT}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/graphbolt)
|
||||
endif(MSVC)
|
||||
endif(BUILD_GRAPHBOLT)
|
||||
@@ -0,0 +1,68 @@
|
||||
## Contributing to DGL
|
||||
|
||||
Contribution is always welcomed. A good starting place is the roadmap issue, where
|
||||
you can find our current milestones. All contributions must go through pull requests
|
||||
and be reviewed by the committers. See our [contribution
|
||||
guide](https://docs.dgl.ai/contribute.html) for more details.
|
||||
|
||||
Once your contribution is accepted and merged, congratulations, you are now a
|
||||
contributor to the DGL project. We will put your name in the list below.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* [Minjie Wang](https://github.com/jermainewang) from AWS
|
||||
* [Da Zheng](https://github.com/zheng-da) from AWS
|
||||
* [Quan Gan](https://github.com/BarclayII) from AWS
|
||||
* [Mufei Li](https://github.com/mufeili) from AWS
|
||||
* [Jinjing Zhou](https://github.com/VoVAllen) from AWS
|
||||
* [Xiang Song](https://github.com/classicsong) from AWS
|
||||
* [Tianjun Xiao](https://github.com/sneakerkg) from AWS
|
||||
* [Tong He](https://github.com/hetong007) from AWS
|
||||
* [Jian Zhang](https://github.com/zhjwy9343) from AWS
|
||||
* [Qipeng Guo](https://github.com/QipengGuo) from AWS
|
||||
* [Xiangkun Hu](https://github.com/HuXiangkun) from AWS
|
||||
* [Ying Rui](https://github.com/Rhett-Ying) from AWS
|
||||
* [Israt Nisa](https://github.com/isratnisa) from AWS
|
||||
* [Zheng Zhang](https://github.com/zzhang-cn) from AWS
|
||||
* [Zihao Ye](https://github.com/yzh119) from University of Washington
|
||||
* [Chao Ma](https://github.com/aksnzhy)
|
||||
* [Qidong](https://github.com/soodoshll)
|
||||
* [Lingfan Yu](https://github.com/lingfanyu) from New York University
|
||||
* [Yu Gai](https://github.com/GaiYu0) from University of California, Berkeyley
|
||||
* [Qi Huang]() from New York University
|
||||
* [Dominique LaSalle](https://github.com/nv-dlasalle) from Nvidia
|
||||
* [Pawel Piotrowcz](https://github.com/pawelpiotrowicz) from Intel
|
||||
* [Michal Szarmach](https://github.com/mszarma) from Intel
|
||||
* [Izabela Mazur](https://github.com/IzabelaMazur) from Intel
|
||||
* [Sanchit Misra](https://github.com/sanchit-misra) from Intel
|
||||
* [Andrzej Kotlowski](https://github.com/anko-intel) from Intel
|
||||
* [Sheng Zha](https://github.com/szha) from AWS
|
||||
* [Yifei Ma](https://github.com/yifeim) from AWS
|
||||
* [Yizhi Liu](https://github.com/yzhliu) from AWS
|
||||
* [Kay Liu](https://github.com/kayzliu) from UIC
|
||||
* [Tianqi Zhang](https://github.com/lygztq) from SJTU
|
||||
* [Hengrui Zhang](https://github.com/hengruizhang98)
|
||||
* [Seung Won Min](https://github.com/davidmin7) from UIUC
|
||||
* [@hbsun2113](https://github.com/hbsun2113): GraphSAGE in PyTorch
|
||||
* [Tianyi Zhang](https://github.com/Tiiiger): SGC in PyTorch
|
||||
* [Jun Chen](https://github.com/kitaev-chen): GIN in PyTorch
|
||||
* [Aymen Waheb](https://github.com/aymenwah): APPNP in PyTorch
|
||||
* [Chengqiang Lu](https://github.com/geekinglcq): MGCN, SchNet and MPNN in PyTorch
|
||||
* [Gongze Cao](https://github.com/Zardinality): Cluster GCN
|
||||
* [Yicheng Wu](https://github.com/MilkshakeForReal): RotatE in PyTorch
|
||||
* [Hao Xiong](https://github.com/ShawXh): DeepWalk in PyTorch
|
||||
* [Zhi Lin](https://github.com/kira-lin): Integrate FeatGraph into DGL
|
||||
* [Andrew Tsesis](https://github.com/noncomputable): Framework-Agnostic Graph Ops
|
||||
* [Brett Koonce](https://github.com/brettkoonce)
|
||||
* [@giuseppefutia](https://github.com/giuseppefutia)
|
||||
* [@mori97](https://github.com/mori97)
|
||||
* [@xnuohz](https://github.com/xnuohz)
|
||||
* [Hao Jin](https://github.com/haojin2) from Amazon
|
||||
* [Xin Yao](https://github.com/yaox12) from Nvidia
|
||||
* [Abdurrahman Yasar](https://github.com/ayasar70) from Nvidia
|
||||
* [Shaked Brody](https://github.com/shakedbr) from Technion
|
||||
* [Jiahui Liu](https://github.com/paoxiaode) from Nvidia
|
||||
* [Neil Dickson](https://github.com/ndickson-nvidia) from Nvidia
|
||||
* [Chang Liu](https://github.com/chang-l) from Nvidia
|
||||
* [Muhammed Fatih Balin](https://github.com/mfbalin) from Nvidia and Georgia Tech
|
||||
Vendored
+679
@@ -0,0 +1,679 @@
|
||||
#!/usr/bin/env groovy
|
||||
|
||||
// CI tests are executed within Docker containers as the 'root' user. However,
|
||||
// communications between Jenkins nodes are done with the 'ubuntu' user(login
|
||||
// via root is disallowed on AWS EC2 instances). Therefore, we need to change
|
||||
// the file permission to allow 'ubuntu' user to access the files created by
|
||||
// the 'root' user. This is achieved by running 'chmod -R 777 .'.
|
||||
|
||||
// Summary of Jenkins nodes:
|
||||
// - linux-benchmark-node: Linux CPU node for authentication and lint check.
|
||||
// number of nodes: 1
|
||||
// instance type: m5.2xlarge(8 vCPUs, 32 GB memory)
|
||||
// number of executors per node: 6
|
||||
// number of jobs running on this node per CI run: 3
|
||||
// - dgl-ci-linux-cpu: Linux CPU node for building and testing.
|
||||
// number of nodes: 4
|
||||
// instance type: m6i.24xlarge(96 vCPUs, 384 GB memory)
|
||||
// number of executors per node: 6
|
||||
// number of jobs running on this node per CI run: 8
|
||||
// - dgl-ci-linux-gpu: Linux GPU node for building and testing.
|
||||
// number of nodes: 4
|
||||
// instance type: g4dn.4xlarge(16 vCPUs, 64 GB memory, 1 GPU)
|
||||
// number of executors per node: 1
|
||||
// number of jobs running on this node per CI run: 4
|
||||
// - dgl-ci-windows-cpu: Windows CPU node for building and testing.
|
||||
// number of nodes: 4
|
||||
// instance type: m6i.8xlarge(32 vCPUs, 128 GB memory)
|
||||
// number of executors per node: 2
|
||||
// number of jobs running on this node per CI run: 3
|
||||
|
||||
dgl_linux_libs = 'build/libdgl.so, build/runUnitTests, python/dgl/_ffi/_cy3/core.cpython-*-x86_64-linux-gnu.so, build/tensoradapter/pytorch/*.so, build/dgl_sparse/*.so, build/graphbolt/*.so'
|
||||
// Currently DGL on Windows is not working with Cython yet
|
||||
dgl_win64_libs = "build\\dgl.dll, build\\runUnitTests.exe, build\\tensoradapter\\pytorch\\*.dll, build\\dgl_sparse\\*.dll, build\\graphbolt\\*.dll"
|
||||
|
||||
def init_git() {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
sh 'rm -rf *'
|
||||
sh "git config --global --add safe.directory '*'"
|
||||
checkout scm
|
||||
sh 'git submodule update --recursive --init'
|
||||
}
|
||||
|
||||
def init_git_win64() {
|
||||
checkout scm
|
||||
bat 'git submodule update --recursive --init'
|
||||
}
|
||||
|
||||
// pack libraries for later use
|
||||
def pack_lib(name, libs) {
|
||||
echo "Packing ${libs} into ${name}"
|
||||
stash includes: libs, name: name
|
||||
}
|
||||
|
||||
// unpack libraries saved before
|
||||
def unpack_lib(name, libs) {
|
||||
unstash name
|
||||
echo "Unpacked ${libs} from ${name}"
|
||||
}
|
||||
|
||||
def build_dgl_linux(dev) {
|
||||
init_git()
|
||||
sh "bash tests/scripts/build_dgl.sh ${dev}"
|
||||
sh 'ls -lh /usr/lib/x86_64-linux-gnu/'
|
||||
pack_lib("dgl-${dev}-linux", dgl_linux_libs)
|
||||
}
|
||||
|
||||
def build_dgl_win64(dev) {
|
||||
/* Assuming that Windows slaves are already configured with MSBuild VS2017,
|
||||
* CMake and Python/pip/setuptools etc. */
|
||||
init_git_win64()
|
||||
bat "CALL tests\\scripts\\build_dgl.bat"
|
||||
pack_lib("dgl-${dev}-win64", dgl_win64_libs)
|
||||
}
|
||||
|
||||
def cpp_unit_test_linux(dev) {
|
||||
init_git()
|
||||
unpack_lib("dgl-${dev}-linux", dgl_linux_libs)
|
||||
sh 'bash tests/scripts/task_cpp_unit_test.sh'
|
||||
}
|
||||
|
||||
def cpp_unit_test_win64() {
|
||||
init_git_win64()
|
||||
unpack_lib('dgl-cpu-win64', dgl_win64_libs)
|
||||
bat "CALL tests\\scripts\\task_cpp_unit_test.bat"
|
||||
}
|
||||
|
||||
def unit_test_linux(backend, dev) {
|
||||
init_git()
|
||||
unpack_lib("dgl-${dev}-linux", dgl_linux_libs)
|
||||
timeout(time: 40, unit: 'MINUTES') {
|
||||
sh "bash tests/scripts/task_unit_test.sh ${backend} ${dev}"
|
||||
}
|
||||
}
|
||||
|
||||
def unit_distributed_linux(backend, dev) {
|
||||
init_git()
|
||||
unpack_lib("dgl-${dev}-linux", dgl_linux_libs)
|
||||
timeout(time: 40, unit: 'MINUTES') {
|
||||
sh "bash tests/scripts/task_distributed_test.sh ${backend} ${dev}"
|
||||
}
|
||||
}
|
||||
|
||||
def unit_test_cugraph(backend, dev) {
|
||||
init_git()
|
||||
unpack_lib("dgl-${dev}-linux", dgl_linux_libs)
|
||||
timeout(time: 15, unit: 'MINUTES') {
|
||||
sh "bash tests/scripts/cugraph_unit_test.sh ${backend}"
|
||||
}
|
||||
}
|
||||
|
||||
def unit_test_win64(backend, dev) {
|
||||
init_git_win64()
|
||||
unpack_lib("dgl-${dev}-win64", dgl_win64_libs)
|
||||
timeout(time: 50, unit: 'MINUTES') {
|
||||
bat "CALL tests\\scripts\\task_unit_test.bat ${backend}"
|
||||
}
|
||||
}
|
||||
|
||||
def example_test_linux(backend, dev) {
|
||||
init_git()
|
||||
unpack_lib("dgl-${dev}-linux", dgl_linux_libs)
|
||||
timeout(time: 20, unit: 'MINUTES') {
|
||||
sh "bash tests/scripts/task_example_test.sh ${dev}"
|
||||
}
|
||||
}
|
||||
|
||||
def example_test_win64(backend, dev) {
|
||||
init_git_win64()
|
||||
unpack_lib("dgl-${dev}-win64", dgl_win64_libs)
|
||||
timeout(time: 20, unit: 'MINUTES') {
|
||||
bat "CALL tests\\scripts\\task_example_test.bat ${dev}"
|
||||
}
|
||||
}
|
||||
|
||||
def tutorial_test_linux(backend) {
|
||||
init_git()
|
||||
unpack_lib('dgl-cpu-linux', dgl_linux_libs)
|
||||
timeout(time: 20, unit: 'MINUTES') {
|
||||
sh "bash tests/scripts/task_${backend}_tutorial_test.sh"
|
||||
}
|
||||
}
|
||||
|
||||
def go_test_linux() {
|
||||
init_git()
|
||||
unpack_lib('dgl-cpu-linux', dgl_linux_libs)
|
||||
timeout(time: 20, unit: 'MINUTES') {
|
||||
sh "bash tests/scripts/task_go_test.sh"
|
||||
}
|
||||
}
|
||||
|
||||
def is_authorized(name) {
|
||||
def devs = [
|
||||
// System:
|
||||
'dgl-bot', 'noreply',
|
||||
// Core:
|
||||
'Rhett-Ying', 'BarclayII', 'jermainewang', 'mufeili', 'isratnisa',
|
||||
'rudongyu', 'classicsong', 'HuXiangkun', 'hetong007', 'kylasa',
|
||||
'frozenbugs', 'peizhou001', 'zheng-da', 'czkkkkkk', 'thvasilo',
|
||||
// Intern:
|
||||
'pyynb', 'az15240', 'BowenYao18', 'kec020', 'Liu-rj',
|
||||
// Friends:
|
||||
'nv-dlasalle', 'yaox12', 'chang-l', 'Kh4L', 'VibhuJawa', 'kkranen',
|
||||
'TristonC', 'mfbalin',
|
||||
'bgawrych', 'itaraban', 'daniil-sizov', 'anko-intel', 'Kacper-Pietkun',
|
||||
'hankaj', 'agrabows', 'DominikaJedynak', 'RafLit', 'CfromBU',
|
||||
// Emeritus:
|
||||
'VoVAllen',
|
||||
]
|
||||
return (name in devs)
|
||||
}
|
||||
|
||||
def is_admin(name) {
|
||||
def admins = ['dgl-bot', 'Rhett-Ying', 'BarclayII', 'jermainewang']
|
||||
return (name in admins)
|
||||
}
|
||||
|
||||
def regression_test_done = false
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
triggers {
|
||||
issueCommentTrigger('@dgl-bot.*')
|
||||
}
|
||||
stages {
|
||||
// Below 2 stages are to authenticate the change/comment author.
|
||||
// Only core developers are allowed to trigger CI.
|
||||
// Such authentication protects CI from malicious code which may bring CI instances down.
|
||||
stage('Authentication') {
|
||||
agent {
|
||||
docker {
|
||||
label 'linux-benchmark-node'
|
||||
image 'dgllib/dgl-ci-lint'
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
when { not { triggeredBy 'IssueCommentCause' } }
|
||||
steps {
|
||||
script {
|
||||
def author = env.CHANGE_AUTHOR
|
||||
def prOpenTriggerCause = currentBuild.getBuildCauses('jenkins.branch.BranchEventCause')
|
||||
def first_run = prOpenTriggerCause && env.BUILD_ID == '1'
|
||||
if (author && !is_authorized(author)) {
|
||||
pullRequest.comment("Not authorized to trigger CI. Please ask core developer to help trigger via issuing comment: \n - `@dgl-bot`")
|
||||
error("Authentication failed.")
|
||||
}
|
||||
if (first_run) {
|
||||
pullRequest.comment('To trigger regression tests: \n - `@dgl-bot run [instance-type] [which tests] [compare-with-branch]`; \n For example: `@dgl-bot run g4dn.4xlarge all dmlc/master` or `@dgl-bot run c5.9xlarge kernel,api dmlc/master`')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('AuthenticationComment') {
|
||||
agent {
|
||||
docker {
|
||||
label 'linux-benchmark-node'
|
||||
image 'dgllib/dgl-ci-lint'
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
when { triggeredBy 'IssueCommentCause' }
|
||||
steps {
|
||||
script {
|
||||
def author = env.GITHUB_COMMENT_AUTHOR
|
||||
if (!is_authorized(author)) {
|
||||
pullRequest.comment("Not authorized to trigger CI via issuing comment.")
|
||||
error("Authentication failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Regression Test') {
|
||||
agent {
|
||||
docker {
|
||||
label 'linux-benchmark-node'
|
||||
image 'dgllib/dgl-ci-lint'
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
when { triggeredBy 'IssueCommentCause' }
|
||||
steps {
|
||||
checkout scm
|
||||
script {
|
||||
def comment = env.GITHUB_COMMENT
|
||||
def command_lists = comment.split(' ')
|
||||
if (command_lists.size() == 1) {
|
||||
// CI command, not for regression
|
||||
return
|
||||
}
|
||||
if (command_lists.size() != 5) {
|
||||
pullRequest.comment('Cannot run the regression test due to unknown command')
|
||||
error('Unknown command')
|
||||
}
|
||||
def author = env.GITHUB_COMMENT_AUTHOR
|
||||
echo("${env.GIT_URL}")
|
||||
echo("${env}")
|
||||
if (!is_admin(author)) {
|
||||
error('Not authorized to launch regression tests')
|
||||
}
|
||||
dir('benchmark_scripts_repo') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']],
|
||||
userRemoteConfigs: [[credentialsId: 'github', url: 'https://github.com/dglai/DGL_scripts.git']]])
|
||||
}
|
||||
sh('cp benchmark_scripts_repo/benchmark/* benchmarks/scripts/')
|
||||
def instance_type = command_lists[2].replace('.', '')
|
||||
pullRequest.comment("Start the Regression test. View at ${RUN_DISPLAY_URL}")
|
||||
def prNumber = env.BRANCH_NAME.replace('PR-', '')
|
||||
dir('benchmarks/scripts') {
|
||||
sh('python3 -m pip install boto3')
|
||||
sh("PYTHONUNBUFFERED=1 GIT_PR_ID=${prNumber} GIT_URL=${env.GIT_URL} GIT_BRANCH=${env.CHANGE_BRANCH} python3 run_reg_test.py --data-folder ${env.GIT_COMMIT}_${instance_type} --run-cmd '${comment}'")
|
||||
}
|
||||
pullRequest.comment("Finished the Regression test. Result table is at https://dgl-asv-data.s3-us-west-2.amazonaws.com/${env.GIT_COMMIT}_${instance_type}/results/result.csv. Jenkins job link is ${RUN_DISPLAY_URL}. ")
|
||||
currentBuild.result = 'SUCCESS'
|
||||
regression_test_done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('CI') {
|
||||
when { expression { !regression_test_done } }
|
||||
stages {
|
||||
stage('Abort Previous CI') {
|
||||
steps {
|
||||
script {
|
||||
if (env.BRANCH_NAME != "master") {
|
||||
// Jenkins will abort an older build if a newer build already
|
||||
// passed a higher milestone.
|
||||
// https://www.jenkins.io/doc/pipeline/steps/pipeline-milestone-step/
|
||||
def buildNumber = env.BUILD_NUMBER as int
|
||||
for (int i = 1; i <= buildNumber; i++) {
|
||||
milestone(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Lint Check') {
|
||||
agent {
|
||||
docker {
|
||||
label "linux-benchmark-node"
|
||||
image "dgllib/dgl-ci-lint"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
init_git()
|
||||
sh 'bash tests/scripts/task_lint.sh'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
parallel {
|
||||
stage('CPU Build') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-cpu"
|
||||
image "dgllib/dgl-ci-cpu:v240511_1440"
|
||||
args "-u root"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
build_dgl_linux('cpu')
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('GPU Build') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-cpu"
|
||||
image "dgllib/dgl-ci-gpu:cu121_v240511_1440"
|
||||
args "-u root"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
// sh "nvidia-smi"
|
||||
build_dgl_linux('gpu')
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('PyTorch Cugraph GPU Build') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-cpu"
|
||||
image "rapidsai/cugraph_stable_torch-cuda:11.8-base-ubuntu20.04-py3.10-pytorch2.0.0-rapids23.04"
|
||||
args "-u root"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
build_dgl_linux('cugraph')
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('CPU Build (Win64)') {
|
||||
agent { label 'dgl-ci-windows-cpu' }
|
||||
steps {
|
||||
build_dgl_win64('cpu')
|
||||
}
|
||||
post {
|
||||
always {
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Currently we don't have Windows GPU build machines
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
parallel {
|
||||
stage('C++ CPU') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-cpu"
|
||||
image "dgllib/dgl-ci-cpu:v240511_1440"
|
||||
args "-u root"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
cpp_unit_test_linux('cpu')
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('C++ GPU') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-gpu"
|
||||
image "dgllib/dgl-ci-gpu:cu121_v240511_1440"
|
||||
args "-u root --runtime nvidia"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
cpp_unit_test_linux('gpu')
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('C++ CPU (Win64)') {
|
||||
agent { label 'dgl-ci-windows-cpu' }
|
||||
steps {
|
||||
cpp_unit_test_win64()
|
||||
}
|
||||
post {
|
||||
always {
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Tensorflow CPU') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-cpu"
|
||||
image "dgllib/dgl-ci-cpu:v230810"
|
||||
args "-u root"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Tensorflow CPU Unit test') {
|
||||
steps {
|
||||
unit_test_linux('tensorflow', 'cpu')
|
||||
}
|
||||
// Tensorflow is deprecated.
|
||||
when { expression { false } }
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Tensorflow GPU') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-gpu"
|
||||
image "dgllib/dgl-ci-gpu:cu121_v240511_1440"
|
||||
args "-u root --runtime nvidia"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Tensorflow GPU Unit test') {
|
||||
steps {
|
||||
unit_test_linux('tensorflow', 'gpu')
|
||||
}
|
||||
// Tensorflow does not support cuda 11.6 yet.
|
||||
when { expression { false } }
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Torch CPU') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-cpu"
|
||||
image "dgllib/dgl-ci-cpu:v240511_1440"
|
||||
args "-u root --shm-size=4gb"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Torch CPU Unit test') {
|
||||
steps {
|
||||
unit_test_linux('pytorch', 'cpu')
|
||||
}
|
||||
}
|
||||
stage('Torch CPU Example test') {
|
||||
steps {
|
||||
example_test_linux('pytorch', 'cpu')
|
||||
}
|
||||
}
|
||||
stage('Torch CPU Tutorial test') {
|
||||
steps {
|
||||
tutorial_test_linux('pytorch')
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Torch CPU (Win64)') {
|
||||
agent { label 'dgl-ci-windows-cpu' }
|
||||
stages {
|
||||
stage('Torch CPU (Win64) Unit test') {
|
||||
steps {
|
||||
unit_test_win64('pytorch', 'cpu')
|
||||
}
|
||||
}
|
||||
stage('Torch CPU (Win64) Example test') {
|
||||
steps {
|
||||
example_test_win64('pytorch', 'cpu')
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Torch GPU') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-gpu"
|
||||
image "dgllib/dgl-ci-gpu:cu121_v240511_1440"
|
||||
args "-u root --runtime nvidia --shm-size=8gb"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Torch GPU Unit test') {
|
||||
steps {
|
||||
sh 'nvidia-smi'
|
||||
unit_test_linux('pytorch', 'gpu')
|
||||
}
|
||||
}
|
||||
stage('Torch GPU Example test') {
|
||||
steps {
|
||||
example_test_linux('pytorch', 'gpu')
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Distributed') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-cpu"
|
||||
image "dgllib/dgl-ci-cpu:v240511_1440"
|
||||
args "-u root --shm-size=8gb"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Distributed Torch CPU Unit test') {
|
||||
steps {
|
||||
unit_distributed_linux('pytorch', 'cpu')
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('PyTorch Cugraph GPU') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-gpu"
|
||||
image "rapidsai/cugraph_stable_torch-cuda:11.8-base-ubuntu20.04-py3.10-pytorch2.0.0-rapids23.04"
|
||||
args "-u root --runtime nvidia --shm-size=8gb"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('PyTorch Cugraph GPU Unit test') {
|
||||
steps {
|
||||
sh 'nvidia-smi'
|
||||
unit_test_cugraph('pytorch', 'cugraph')
|
||||
}
|
||||
// Cugraph is under refactoring. Skip the test for now.
|
||||
when { expression { false } }
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('DGL-Go') {
|
||||
agent {
|
||||
docker {
|
||||
label "dgl-ci-linux-cpu"
|
||||
image "dgllib/dgl-ci-cpu:v240511_1440"
|
||||
args "-u root"
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('DGL-Go CPU test') {
|
||||
steps {
|
||||
go_test_linux()
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh "chmod -R 777 ." // Fix permission issue
|
||||
cleanWs disableDeferredWipeout: true, deleteDirs: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
node("dglci-post-linux") {
|
||||
docker.image('dgllib/dgl-ci-awscli:v220418').inside("--pull always --entrypoint=''") {
|
||||
sh("rm -rf ci_tmp")
|
||||
dir('ci_tmp') {
|
||||
sh("curl -k -o cireport.log ${BUILD_URL}consoleText")
|
||||
sh("curl -o report.py https://raw.githubusercontent.com/dmlc/dgl/master/tests/scripts/ci_report/report.py")
|
||||
sh("curl -o status.py https://raw.githubusercontent.com/dmlc/dgl/master/tests/scripts/ci_report/status.py")
|
||||
sh("curl -k -L ${BUILD_URL}wfapi")
|
||||
sh("cat status.py")
|
||||
sh("pytest --html=report.html --self-contained-html report.py || true")
|
||||
sh("aws s3 sync ./ s3://dgl-ci-result/${JOB_NAME}/${BUILD_NUMBER}/${BUILD_ID}/logs/ --exclude '*' --include '*.log' --acl public-read --content-type text/plain")
|
||||
sh("aws s3 sync ./ s3://dgl-ci-result/${JOB_NAME}/${BUILD_NUMBER}/${BUILD_ID}/logs/ --exclude '*.log' --acl public-read")
|
||||
|
||||
def comment = sh(returnStdout: true, script: "python3 status.py --result ${currentBuild.currentResult}").trim()
|
||||
echo(comment)
|
||||
if ((env.BRANCH_NAME).startsWith('PR-')) {
|
||||
pullRequest.comment(comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node('dgl-ci-windows-cpu') {
|
||||
bat(script: "rmvirtualenv ${BUILD_TAG}", returnStatus: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,77 @@
|
||||
DGL release and change logs
|
||||
==========
|
||||
|
||||
Refer to the roadmap issue for the on-going versions and features.
|
||||
|
||||
0.2
|
||||
---
|
||||
Major release that includes many features, bugfix and performance improvement.
|
||||
Speed of GCN model on Pubmed dataset has been improved by **4.19x**! Speed of
|
||||
RGCN model on Mutag dataset has been improved by **7.35x**! Important new
|
||||
feature: **graph sampling APIs**.
|
||||
|
||||
Update details:
|
||||
|
||||
# Model examples
|
||||
- [x] TreeLSTM w/ MXNet (PR #279 by @szha )
|
||||
- [x] GraphSage (@ZiyueHuang )
|
||||
- [x] Improve GAT model speed (PR #348 by @jermainewang )
|
||||
|
||||
# Core system improvement
|
||||
- [x] Immutable CSR graph structure (PR #342 by @zheng-da )
|
||||
- [x] Finish remaining functionality (Issue #369, PR #404 by @yzh119)
|
||||
- [x] Nodeflow data structure (PR #361 by @zheng-da )
|
||||
- [x] Neighbor sampler (PR #322 )
|
||||
- [x] Layer-wise sampler (PR #362 by @GaiYu0 )
|
||||
- [x] Multi-GPU support by data parallelism (PR #356 #338 by @ylfdq1118 )
|
||||
- [x] More dataset:
|
||||
- [x] Reddit dataset loader (PR #372 by @ZiyueHuang )
|
||||
- [x] PPI dataset loader (PR #395 by @sufeidechabei )
|
||||
- [x] Mini graph classification dataset (PR #364 by @mufeili )
|
||||
- [x] NN modules (PR #406 by @jermainewang @mufeili)
|
||||
- [x] GraphConv layer
|
||||
- [x] Edge softmax layer
|
||||
- [x] Edge group apply API (PR #358 by @VoVAllen )
|
||||
- [x] Reversed graph and transform.py module (PR #331 by @mufeili )
|
||||
- [x] Max readout (PR #341 by @mufeili )
|
||||
- [x] Random walk APIs (PR #392 by @BarclayII )
|
||||
|
||||
# Tutorial/Blog
|
||||
- [x] Batched graph classification in DGL (PR #360 by @mufeili )
|
||||
- [x] Understanding GAT (@sufeidechabei )
|
||||
|
||||
# Project improvement
|
||||
- [x] Python lint check (PR #330 by @jermainewang )
|
||||
- [x] Win CI (PR #324 by @BarclayII )
|
||||
- [x] Auto doc build (by @VoVAllen )
|
||||
- [x] Unify tests for different backends (PR #333 by @BarclayII )
|
||||
|
||||
0.1.3
|
||||
-----
|
||||
Bug fix
|
||||
* Compatible with Pytorch v1.0
|
||||
* Bug fix in networkx graph conversion.
|
||||
|
||||
0.1.2
|
||||
-----
|
||||
First open release.
|
||||
* Basic graph APIs.
|
||||
* Basic message passing APIs.
|
||||
* Pytorch backend.
|
||||
* MXNet backend.
|
||||
* Optimization using SPMV.
|
||||
* Model examples w/ Pytorch:
|
||||
- GCN
|
||||
- GAT
|
||||
- JTNN
|
||||
- DGMG
|
||||
- Capsule
|
||||
- LGNN
|
||||
- RGCN
|
||||
- Transformer
|
||||
- TreeLSTM
|
||||
* Model examples w/ MXNet:
|
||||
- GCN
|
||||
- GAT
|
||||
- RGCN
|
||||
- SSE
|
||||
@@ -0,0 +1,328 @@
|
||||
<p align="center">
|
||||
<img src="http://data.dgl.ai/asset/logo.jpg" height="200">
|
||||
</p>
|
||||
|
||||
[](https://github.com/dmlc/dgl/releases)
|
||||
[](https://anaconda.org/dglteam/dgl)
|
||||
[](https://ci.dgl.ai/job/DGL/job/master/)
|
||||
[](https://asv.dgl.ai/)
|
||||
[](./LICENSE)
|
||||
[](https://twitter.com/GraphDeep)
|
||||
|
||||
[Website](https://www.dgl.ai) | [A Blitz Introduction to DGL](https://docs.dgl.ai/tutorials/blitz/index.html) | Documentation ([Latest](https://www.dgl.ai/dgl_docs/) | [Official Examples](examples/README.md) | [Discussion Forum](https://discuss.dgl.ai) | [Slack Channel](https://join.slack.com/t/deep-graph-library/shared_invite/zt-eb4ict1g-xcg3PhZAFAB8p6dtKuP6xQ)
|
||||
|
||||
DGL is an easy-to-use, high performance and scalable Python package for deep learning on graphs. DGL is framework agnostic, meaning if a deep graph model is a component of an end-to-end application, the rest of the logics can be implemented in any major frameworks, such as PyTorch, Apache MXNet or TensorFlow.
|
||||
|
||||
<p align="center">
|
||||
<img src="http://data.dgl.ai/asset/image/DGL-Arch.png" alt="DGL v0.4 architecture" width="600">
|
||||
<br>
|
||||
<b>Figure</b>: DGL Overall Architecture
|
||||
</p>
|
||||
|
||||
## Highlighted Features
|
||||
|
||||
### A GPU-ready graph library
|
||||
|
||||
DGL provides a powerful graph object that can reside on either CPU or GPU. It bundles structural data as well as features for better control. We provide a variety of functions for computing with graph objects including efficient and customizable message passing primitives for Graph Neural Networks.
|
||||
|
||||
### A versatile tool for GNN researchers and practitioners
|
||||
|
||||
The field of graph deep learning is still rapidly evolving and many research ideas emerge by standing on the shoulders of giants. To ease the process, [DGl-Go](https://github.com/dmlc/dgl/tree/master/dglgo) is a command-line interface to get started with training, using and studying state-of-the-art GNNs.
|
||||
DGL collects a rich set of [example implementations](https://github.com/dmlc/dgl/tree/master/examples) of popular GNN models of a wide range of topics. Researchers can [search](https://www.dgl.ai/) for related models to innovate new ideas from or use them as baselines for experiments. Moreover, DGL provides many state-of-the-art [GNN layers and modules](https://docs.dgl.ai/api/python/nn.html) for users to build new model architectures. DGL is one of the preferred platforms for many standard graph deep learning benchmarks including [OGB](https://ogb.stanford.edu/) and [GNNBenchmarks](https://github.com/graphdeeplearning/benchmarking-gnns).
|
||||
|
||||
### Easy to learn and use
|
||||
|
||||
DGL provides plenty of learning materials for all kinds of users from ML researchers to domain experts. The [Blitz Introduction to DGL](https://docs.dgl.ai/tutorials/blitz/index.html) is a 120-minute tour of the basics of graph machine learning. The [User Guide](https://docs.dgl.ai/guide/index.html) explains in more details the concepts of graphs as well as the training methodology. All of them include code snippets in DGL that are runnable and ready to be plugged into one’s own pipeline.
|
||||
|
||||
### Scalable and efficient
|
||||
|
||||
It is convenient to train models using DGL on large-scale graphs across **multiple GPUs** or **multiple machines**. DGL extensively optimizes the whole stack to reduce the overhead in communication, memory consumption and synchronization. As a result, DGL can easily scale to billion-sized graphs. Get started with the [tutorials](https://docs.dgl.ai/en/tutorials/dist/index.html) and [user guide](https://docs.dgl.ai/en/latest/guide/distributed.html) for distributed training. See the [system performance note](https://docs.dgl.ai/performance.html) for the comparison with other tools.
|
||||
|
||||
## Get Started
|
||||
|
||||
Users can install DGL from [pip and conda](https://www.dgl.ai/pages/start.html). You can also download GPU enabled DGL docker [containers](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/dgl) (backended by PyTorch) from NVIDIA NGC for both x86 and ARM based linux systems. Advanced users can follow the [instructions](https://docs.dgl.ai/install/index.html#install-from-source) to install from source.
|
||||
|
||||
For absolute beginners, start with [the Blitz Introduction to DGL](https://docs.dgl.ai/tutorials/blitz/index.html). It covers the basic concepts of common graph machine learning tasks and a step-by-step on building Graph Neural Networks (GNNs) to solve them.
|
||||
|
||||
For acquainted users who wish to learn more,
|
||||
|
||||
* Experience state-of-the-art GNN models in only two command-lines using [DGL-Go](https://github.com/dmlc/dgl/tree/master/dglgo).
|
||||
* Learn DGL by [example implementations](https://www.dgl.ai/) of popular GNN models.
|
||||
* Read the [User Guide](https://docs.dgl.ai/guide/index.html) ([中文版链接](https://docs.dgl.ai/guide_cn/index.html)), which explains the concepts and usage of DGL in much more details.
|
||||
* Go through the tutorials for advanced features like [stochastic training of GNNs](https://docs.dgl.ai/tutorials/large/index.html), training on [multi-GPU](https://docs.dgl.ai/tutorials/multi/index.html) or [multi-machine](https://docs.dgl.ai/tutorials/dist/index.html).
|
||||
* [Study classical papers](https://docs.dgl.ai/tutorials/models/index.html) on graph machine learning alongside DGL.
|
||||
* Search for the usage of a specific API in the [API reference manual](https://docs.dgl.ai/api/python/index.html), which organizes all DGL APIs by their namespace.
|
||||
|
||||
All the learning materials are available at our [documentation site](https://docs.dgl.ai/). If you are new to deep learning in general,
|
||||
check out the open source book [Dive into Deep Learning](https://d2l.ai/).
|
||||
|
||||
|
||||
## Community
|
||||
|
||||
### Get connected
|
||||
|
||||
We provide multiple channels to connect you to the community of the DGL developers, users, and the general GNN academic researchers:
|
||||
|
||||
* Our Slack channel, [click to join](https://join.slack.com/t/deep-graph-library/shared_invite/zt-eb4ict1g-xcg3PhZAFAB8p6dtKuP6xQ)
|
||||
* Our discussion forum: https://discuss.dgl.ai/
|
||||
* Our [Zhihu blog (in Chinese)](https://www.zhihu.com/column/c_1070749881013936128)
|
||||
* Monthly GNN User Group online seminar ([event link](https://www.eventbrite.com/e/graph-neural-networks-user-group-tickets-137512275919?utm-medium=discovery&utm-campaign=social&utm-content=attendeeshare&aff=escb&utm-source=cp&utm-term=listing) | [past videos](https://www.youtube.com/channel/UCnmuSDY1pTlaFH1WRQElfTg))
|
||||
|
||||
Take the survey [here](https://forms.gle/Ej3jHCocACmb49Gp8) and leave any feedback to make DGL better fit for your needs. Thanks!
|
||||
|
||||
### DGL-powered projects
|
||||
|
||||
* DGL-LifeSci: a DGL-based package for various applications in life science with graph neural networks. https://github.com/awslabs/dgl-lifesci
|
||||
* DGL-KE: a high performance, easy-to-use, and scalable package for learning large-scale knowledge graph embeddings. https://github.com/awslabs/dgl-ke
|
||||
* Benchmarking GNN: https://github.com/graphdeeplearning/benchmarking-gnns
|
||||
* OGB: a collection of realistic, large-scale, and diverse benchmark datasets for machine learning on graphs. https://ogb.stanford.edu/
|
||||
* Graph4NLP: an easy-to-use library for R&D at the intersection of Deep Learning on Graphs and Natural Language Processing. https://github.com/graph4ai/graph4nlp
|
||||
* GNN-RecSys: https://github.com/je-dbl/GNN-RecSys
|
||||
* Amazon Neptune ML: a new capability of Neptune that uses Graph Neural Networks (GNNs), a machine learning technique purpose-built for graphs, to make easy, fast, and more accurate predictions using graph data. https://aws.amazon.com/cn/neptune/machine-learning/
|
||||
* GNNLens2: Visualization tool for Graph Neural Networks. https://github.com/dmlc/GNNLens2
|
||||
* RNAGlib: A package to facilitate construction, analysis, visualization and machine learning on RNA 2.5D Graphs. Includes a pre-built dataset: https://rnaglib.cs.mcgill.ca
|
||||
* OpenHGNN: Model zoo and benchmarks for Heterogeneous Graph Neural Networks. https://github.com/BUPT-GAMMA/OpenHGNN
|
||||
* TGL: A graph learning framework for large-scale temporal graphs. https://github.com/amazon-research/tgl
|
||||
* gtrick: Bag of Tricks for Graph Neural Networks. https://github.com/sangyx/gtrick
|
||||
* ArangoDB-DGL Adapter: Import [ArangoDB](https://github.com/arangodb/arangodb) graphs into DGL and vice-versa. https://github.com/arangoml/dgl-adapter
|
||||
* DGLD: [DGLD](https://github.com/EagleLab-ZJU/DGLD) is an open-source library for Deep Graph Anomaly Detection based on pytorch and DGL.
|
||||
### Awesome Papers Using DGL
|
||||
|
||||
1. [**Benchmarking Graph Neural Networks**](https://arxiv.org/pdf/2003.00982.pdf), *Vijay Prakash Dwivedi, Chaitanya K. Joshi, Thomas Laurent, Yoshua Bengio, Xavier Bresson*
|
||||
|
||||
1. [**Open Graph Benchmarks: Datasets for Machine Learning on Graphs**](https://arxiv.org/pdf/2005.00687.pdf), NeurIPS'20, *Weihua Hu, Matthias Fey, Marinka Zitnik, Yuxiao Dong, Hongyu Ren, Bowen Liu, Michele Catasta, Jure Leskovec*
|
||||
|
||||
1. [**DropEdge: Towards Deep Graph Convolutional Networks on Node Classification**](https://openreview.net/pdf?id=Hkx1qkrKPr), ICLR'20, *Yu Rong, Wenbing Huang, Tingyang Xu, Junzhou Huan*
|
||||
|
||||
1. [**Discourse-Aware Neural Extractive Text Summarization**](https://www.aclweb.org/anthology/2020.acl-main.451/), ACL'20, *Jiacheng Xu, Zhe Gan, Yu Cheng, Jingjing Liu*
|
||||
|
||||
1. [**GCC: Graph Contrastive Coding for Graph Neural Network Pre-Training**](https://dl.acm.org/doi/pdf/10.1145/3394486.3403168?casa_token=EClsH2Vc4DcAAAAA:LIB8cbtr6yTDbYuv4cTLwTIYeDq5Y2dhj_ktcWdKpzdPLGeiuL0o8GlcN4QIOnpsAnmGeGVZ), KDD'20, *Jiezhong Qiu, Qibin Chen, Yuxiao Dong, Jing Zhang, Hongxia Yang, Ming Ding, Kuansan Wang, Jie Tang*
|
||||
|
||||
1. [**DGL-KE: Training Knowledge Graph Embeddings at Scale**](https://arxiv.org/pdf/2004.08532), SIGIR'20, *Da Zheng, Xiang Song, Chao Ma, Zeyuan Tan, Zihao Ye, Jin Dong, Hao Xiong, Zheng Zhang, George Karypis*
|
||||
|
||||
1. [**Improving Graph Neural Network Expressivity via Subgraph Isomorphism Counting**](https://arxiv.org/pdf/2006.09252.pdf), *Giorgos Bouritsas, Fabrizio Frasca, Stefanos Zafeiriou, Michael M. Bronstein*
|
||||
|
||||
1. [**INT: An Inequality Benchmark for Evaluating Generalization in Theorem Proving**](https://arxiv.org/pdf/2007.02924.pdf), *Yuhuai Wu, Albert Q. Jiang, Jimmy Ba, Roger Grosse*
|
||||
|
||||
1. [**Finding Patient Zero: Learning Contagion Source with Graph Neural Networks**](https://arxiv.org/pdf/2006.11913.pdf), *Chintan Shah, Nima Dehmamy, Nicola Perra, Matteo Chinazzi, Albert-László Barabási, Alessandro Vespignani, Rose Yu*
|
||||
|
||||
1. [**FeatGraph: A Flexible and Efficient Backend for Graph Neural Network Systems**](https://arxiv.org/pdf/2008.11359.pdf), SC'20, *Yuwei Hu, Zihao Ye, Minjie Wang, Jiali Yu, Da Zheng, Mu Li, Zheng Zhang, Zhiru Zhang, Yida Wang*
|
||||
|
||||
|
||||
<details><summary>more</summary>
|
||||
|
||||
11. [**BP-Transformer: Modelling Long-Range Context via Binary Partitioning.**](https://arxiv.org/pdf/1911.04070.pdf), *Zihao Ye, Qipeng Guo, Quan Gan, Xipeng Qiu, Zheng Zhang*
|
||||
|
||||
12. [**OptiMol: Optimization of Binding Affinities in Chemical Space for Drug Discovery**](https://www.biorxiv.org/content/biorxiv/early/2020/06/16/2020.05.23.112201.full.pdf), *Jacques Boitreaud,Vincent Mallet, Carlos Oliver, Jérôme Waldispühl*
|
||||
|
||||
1. [**JAKET: Joint Pre-training of Knowledge Graph and Language Understanding**](https://arxiv.org/pdf/2010.00796.pdf), *Donghan Yu, Chenguang Zhu, Yiming Yang, Michael Zeng*
|
||||
|
||||
1. [**Architectural Implications of Graph Neural Networks**](https://arxiv.org/pdf/2009.00804.pdf), *Zhihui Zhang, Jingwen Leng, Lingxiao Ma, Youshan Miao, Chao Li, Minyi Guo*
|
||||
|
||||
1. [**Combining Reinforcement Learning and Constraint Programming for Combinatorial Optimization**](https://arxiv.org/pdf/2006.01610.pdf), *Quentin Cappart, Thierry Moisan, Louis-Martin Rousseau1, Isabeau Prémont-Schwarz, and Andre Cire*
|
||||
|
||||
1. [**Therapeutics Data Commons: Machine Learning Datasets and Tasks for Therapeutics**](https://arxiv.org/abs/2102.09548) ([code repo](https://github.com/mims-harvard/TDC)), *Kexin Huang, Tianfan Fu, Wenhao Gao, Yue Zhao, Yusuf Roohani, Jure Leskovec, Connor W. Coley, Cao Xiao, Jimeng Sun, Marinka Zitnik*
|
||||
|
||||
1. [**Sparse Graph Attention Networks**](https://arxiv.org/abs/1912.00552), *Yang Ye, Shihao Ji*
|
||||
|
||||
1. [**On Self-Distilling Graph Neural Network**](https://arxiv.org/pdf/2011.02255.pdf), *Yuzhao Chen, Yatao Bian, Xi Xiao, Yu Rong, Tingyang Xu, Junzhou Huang*
|
||||
|
||||
1. [**Learning Robust Node Representations on Graphs**](https://arxiv.org/pdf/2008.11416.pdf), *Xu Chen, Ya Zhang, Ivor Tsang, and Yuangang Pan*
|
||||
|
||||
1. [**Recurrent Event Network: Autoregressive Structure Inference over Temporal Knowledge Graphs**](https://arxiv.org/abs/1904.05530), *Woojeong Jin, Meng Qu, Xisen Jin, Xiang Ren*
|
||||
|
||||
1. [**Graph Neural Ordinary Differential Equations**](https://arxiv.org/abs/1911.07532), *Michael Poli, Stefano Massaroli, Junyoung Park, Atsushi Yamashita, Hajime Asama, Jinkyoo Park*
|
||||
|
||||
1. [**FusedMM: A Unified SDDMM-SpMM Kernel for Graph Embedding and Graph Neural Networks**](https://arxiv.org/pdf/2011.06391.pdf), *Md. Khaledur Rahman, Majedul Haque Sujon, , Ariful Azad*
|
||||
|
||||
1. [**An Efficient Neighborhood-based Interaction Model for Recommendation on Heterogeneous Graph**](https://arxiv.org/pdf/2007.00216.pdf), KDD'20 *Jiarui Jin, Jiarui Qin, Yuchen Fang, Kounianhua Du, Weinan Zhang, Yong Yu, Zheng Zhang, Alexander J. Smola*
|
||||
|
||||
1. [**Learning Interaction Models of Structured Neighborhood on Heterogeneous Information Network**](https://arxiv.org/pdf/2011.12683.pdf), *Jiarui Jin, Kounianhua Du, Weinan Zhang, Jiarui Qin, Yuchen Fang, Yong Yu, Zheng Zhang, Alexander J. Smola*
|
||||
|
||||
1. [**Graphein - a Python Library for Geometric Deep Learning and Network Analysis on Protein Structures**](https://www.biorxiv.org/content/10.1101/2020.07.15.204701v1), *Arian R. Jamasb, Pietro Lió, Tom L. Blundell*
|
||||
|
||||
1. [**Graph Policy Gradients for Large Scale Robot Control**](https://arxiv.org/abs/1907.03822), *Arbaaz Khan, Ekaterina Tolstaya, Alejandro Ribeiro, Vijay Kumar*
|
||||
|
||||
1. [**Heterogeneous Molecular Graph Neural Networks for Predicting Molecule Properties**](https://arxiv.org/abs/2009.12710), *Zeren Shui, George Karypis*
|
||||
|
||||
1. [**Could Graph Neural Networks Learn Better Molecular Representation for Drug Discovery? A Comparison Study of Descriptor-based and Graph-based Models**](https://assets.researchsquare.com/files/rs-81439/v1_stamped.pdf), *Dejun Jiang, Zhenxing Wu, Chang-Yu Hsieh, Guangyong Chen, Ben Liao, Zhe Wang, Chao Shen, Dongsheng Cao, Jian Wu, Tingjun Hou*
|
||||
|
||||
1. [**Principal Neighbourhood Aggregation for Graph Nets**](https://arxiv.org/abs/2004.05718), *Gabriele Corso, Luca Cavalleri, Dominique Beaini, Pietro Liò, Petar Veličković*
|
||||
|
||||
1. [**Collective Multi-type Entity Alignment Between Knowledge Graphs**](https://dl.acm.org/doi/abs/10.1145/3366423.3380289), *Qi Zhu, Hao Wei, Bunyamin Sisman, Da Zheng, Christos Faloutsos, Xin Luna Dong, Jiawei Han*
|
||||
|
||||
1. [**Graph Representation Forecasting of Patient's Medical Conditions: towards A Digital Twin**](https://arxiv.org/abs/2009.08299), *Pietro Barbiero, Ramon Viñas Torné, Pietro Lió*
|
||||
|
||||
1. [**Relational Graph Learning on Visual and Kinematics Embeddings for Accurate Gesture Recognition in Robotic Surgery**](https://arxiv.org/abs/2011.01619), *Yong-Hao Long, Jie-Ying Wu, Bo Lu, Yue-Ming Jin, Mathias Unberath, Yun-Hui Liu, Pheng-Ann Heng and Qi Dou*
|
||||
|
||||
1. [**Dark Reciprocal-Rank: Boosting Graph-Convolutional Self-Localization Network via Teacher-to-student Knowledge Transfer**](https://arxiv.org/abs/2011.00402), *Takeda Koji, Tanaka Kanji*
|
||||
|
||||
1. [**Graph InfoClust: Leveraging Cluster-Level Node Information For Unsupervised Graph Representation Learning**](https://arxiv.org/abs/2009.06946), *Costas Mavromatis, George Karypis*
|
||||
|
||||
1. [**GraphSeam: Supervised Graph Learning Framework for Semantic UV Mapping**](https://arxiv.org/abs/2011.13748), *Fatemeh Teimury, Bruno Roy, Juan Sebastian Casallas, David macdonald, Mark Coates*
|
||||
|
||||
1. [**Comprehensive Study on Molecular Supervised Learning with Graph Neural Networks**](https://pubs.acs.org/doi/10.1021/acs.jcim.0c00416), *Doyeong Hwang, Soojung Yang, Yongchan Kwon, Kyung Hoon Lee, Grace Lee, Hanseok Jo, Seyeol Yoon, and Seongok Ryu*
|
||||
|
||||
1. [**A graph auto-encoder model for miRNA-disease associations prediction**](https://academic.oup.com/bib/advance-article-abstract/doi/10.1093/bib/bbaa240/5929824?redirectedFrom=fulltext), *Zhengwei Li, Jiashu Li, Ru Nie, Zhu-Hong You, Wenzheng Bao*
|
||||
|
||||
1. [**Graph convolutional regression of cardiac depolarization from sparse endocardial maps**](https://arxiv.org/abs/2009.14068), STACOM 2020 workshop, *Felix Meister, Tiziano Passerini, Chloé Audigier, Èric Lluch, Viorel Mihalef, Hiroshi Ashikaga, Andreas Maier, Henry Halperin, Tommaso Mansi*
|
||||
|
||||
1. [**AttnIO: Knowledge Graph Exploration with In-and-Out Attention Flow for Knowledge-Grounded Dialogue**](https://www.aclweb.org/anthology/2020.emnlp-main.280/), EMNLP'20, *Jaehun Jung, Bokyung Son, Sungwon Lyu*
|
||||
|
||||
1. [**Learning from Non-Binary Constituency Trees via Tensor Decomposition**](https://github.com/danielecastellana22/tensor-tree-nn), COLING'20, *Daniele Castellana, Davide Bacciu*
|
||||
|
||||
1. [**Inducing Alignment Structure with Gated Graph Attention Networks for Sentence Matching**](https://arxiv.org/abs/2010.07668), *Peng Cui, Le Hu, Yuanchao Liu*
|
||||
|
||||
1. [**Enhancing Extractive Text Summarization with Topic-Aware Graph Neural Networks**](https://arxiv.org/abs/2010.06253), COLING'20, *Peng Cui, Le Hu, Yuanchao Liu*
|
||||
|
||||
1. [**Double Graph Based Reasoning for Document-level Relation Extraction**](https://arxiv.org/abs/2009.13752), EMNLP'20, *Shuang Zeng, Runxin Xu, Baobao Chang, Lei Li*
|
||||
|
||||
1. [**Systematic Generalization on gSCAN with Language Conditioned Embedding**](https://arxiv.org/abs/2009.05552), AACL-IJCNLP'20, *Tong Gao, Qi Huang, Raymond J. Mooney*
|
||||
|
||||
1. [**Automatic selection of clustering algorithms using supervised graph embedding**](https://arxiv.org/pdf/2011.08225.pdf), *Noy Cohen-Shapira, Lior Rokach*
|
||||
|
||||
1. [**Improving Learning to Branch via Reinforcement Learning**](https://openreview.net/forum?id=z4D7-PTxTb), *Haoran Sun, Wenbo Chen, Hui Li, Le Song*
|
||||
|
||||
1. [**A Practical Guide to Graph Neural Networks**](https://arxiv.org/pdf/2010.05234.pdf), *Isaac Ronald Ward, Jack Joyner, Casey Lickfold, Stash Rowe, Yulan Guo, Mohammed Bennamoun*, [code](https://github.com/isolabs/gnn-tutorial)
|
||||
|
||||
1. [**APAN: Asynchronous Propagation Attention Network for Real-time Temporal Graph Embedding**](https://arxiv.org/pdf/2011.11545.pdf), SIGMOD'21, *Xuhong Wang, Ding Lyu, Mengjian Li, Yang Xia, Qi Yang, Xinwen Wang, Xinguang Wang, Ping Cui, Yupu Yang, Bowen Sun, Zhenyu Guo, Junkui Li*
|
||||
|
||||
1. [**Uncertainty-Matching Graph Neural Networks to Defend Against Poisoning Attacks**](https://arxiv.org/pdf/2009.14455.pdf), *Uday Shankar Shanthamallu, Jayaraman J. Thiagarajan, Andreas Spanias*
|
||||
|
||||
1. [**Computing Graph Neural Networks: A Survey from Algorithms to Accelerators**](https://arxiv.org/pdf/2010.00130.pdf), *Sergi Abadal, Akshay Jain, Robert Guirado, Jorge López-Alonso, Eduard Alarcón*
|
||||
|
||||
1. [**NHK_STRL at WNUT-2020 Task 2: GATs with Syntactic Dependencies as Edges and CTC-based Loss for Text Classification**](https://www.aclweb.org/anthology/2020.wnut-1.43.pdf), *Yuki Yasuda, Taichi Ishiwatari, Taro Miyazaki, Jun Goto*
|
||||
|
||||
1. [**Relation-aware Graph Attention Networks with Relational Position Encodings for Emotion Recognition in Conversations**](https://www.aclweb.org/anthology/2020.emnlp-main.597.pdf), *Taichi Ishiwatari, Yuki Yasuda, Taro Miyazaki, Jun Goto*
|
||||
|
||||
1. [**PGM-Explainer: Probabilistic Graphical Model Explanations for Graph Neural Networks**](https://proceedings.neurips.cc/paper/2020/file/8fb134f258b1f7865a6ab2d935a897c9-Paper.pdf), *Minh N. Vu, My T. Thai*
|
||||
|
||||
1. [**A Generalization of Transformer Networks to Graphs**](https://arxiv.org/pdf/2012.09699.pdf), *Vijay Prakash Dwivedi, Xavier Bresson*
|
||||
|
||||
1. [**Discourse-Aware Neural Extractive Text Summarization**](https://www.aclweb.org/anthology/2020.acl-main.451.pdf), ACL'20, *Jiacheng Xu, Zhe Gan, Yu Cheng, Jingjing Liu*
|
||||
|
||||
1. [**Learning Robust Node Representations on Graphs**](https://arxiv.org/abs/2008.11416), *Xu Chen, Ya Zhang, Ivor Tsang, Yuangang Pan*
|
||||
|
||||
1. [**Adaptive Graph Diffusion Networks with Hop-wise Attention**](https://arxiv.org/abs/2012.15024), *Chuxiong Sun, Guoshi Wu*
|
||||
|
||||
1. [**The Photoswitch Dataset: A Molecular Machine Learning Benchmark for the Advancement of Synthetic Chemistry**](https://arxiv.org/abs/2008.03226), *Aditya R. Thawani, Ryan-Rhys Griffiths, Arian Jamasb, Anthony Bourached, Penelope Jones, William McCorkindale, Alexander A. Aldrick, Alpha A. Lee*
|
||||
|
||||
1. [**A community-powered search of machine learning strategy space to find NMR property prediction models**](https://arxiv.org/abs/2008.05994), *Lars A. Bratholm, Will Gerrard, Brandon Anderson, Shaojie Bai, Sunghwan Choi, Lam Dang, Pavel Hanchar, Addison Howard, Guillaume Huard, Sanghoon Kim, Zico Kolter, Risi Kondor, Mordechai Kornbluth, Youhan Lee, Youngsoo Lee, Jonathan P. Mailoa, Thanh Tu Nguyen, Milos Popovic, Goran Rakocevic, Walter Reade, Wonho Song, Luka Stojanovic, Erik H. Thiede, Nebojsa Tijanic, Andres Torrubia, Devin Willmott, Craig P. Butts, David R. Glowacki, Kaggle participants*
|
||||
|
||||
1. [**Adaptive Layout Decomposition with Graph Embedding Neural Networks**](http://www.cse.cuhk.edu.hk/~byu/papers/C98-DAC2020-MPL-Selector.pdf), *Wei Li, Jialu Xia, Yuzhe Ma, Jialu Li, Yibo Lin, Bei Yu*, DAC'20
|
||||
|
||||
1. [**Transfer Learning with Graph Neural Networks for Optoelectronic Properties of Conjugated Oligomers**](https://aip.scitation.org/doi/10.1063/5.0037863), J. Chem. Phys. 154, *Chee-Kong Lee, Chengqiang Lu, Yue Yu, Qiming Sun, Chang-Yu Hsieh, Shengyu Zhang, Qi Liu, and Liang Shi*
|
||||
|
||||
1. [**Jet tagging in the Lund plane with graph networks**](https://link.springer.com/article/10.1007/JHEP03(2021)052), Journal of High Energy Physics 2021, *Frédéric A. Dreyer and Huilin Qu*
|
||||
|
||||
1. [**Global Attention Improves Graph Networks Generalization**](https://arxiv.org/abs/2006.07846), *Omri Puny, Heli Ben-Hamu, and Yaron Lipman*
|
||||
|
||||
1. [**Learning over Families of Sets -- Hypergraph Representation Learning for Higher Order Tasks**](https://arxiv.org/abs/2101.07773), SDM 2021, *Balasubramaniam Srinivasan, Da Zheng, and George Karypis*
|
||||
|
||||
1. [**SSFG: Stochastically Scaling Features and Gradients for Regularizing Graph Convolution Networks**](https://arxiv.org/abs/2102.10338), *Haimin Zhang, Min Xu*
|
||||
|
||||
1. [**Application and evaluation of knowledge graph embeddings in biomedical data**](https://peerj.com/articles/cs-341/), PeerJ Computer Science 7:e341, *Mona Alshahrani, Maha A. Thafar, Magbubah Essack*
|
||||
|
||||
1. [**MoTSE: an interpretable task similarity estimator for small molecular property prediction tasks**](https://www.biorxiv.org/content/10.1101/2021.01.13.426608v2), bioRxiv 2021.01.13.426608, *Han Li, Xinyi Zhao, Shuya Li, Fangping Wan, Dan Zhao, Jianyang Zeng*
|
||||
|
||||
1. [**Reinforcement Learning For Data Poisoning on Graph Neural Networks**](https://arxiv.org/abs/2102.06800), *Jacob Dineen, A S M Ahsan-Ul Haque, Matthew Bielskas*
|
||||
|
||||
1. [**Generalising Recursive Neural Models by Tensor Decomposition**](https://github.com/danielecastellana22/tensor-tree-nn), IJCNN'20, *Daniele Castellana, Davide Bacciu*
|
||||
|
||||
1. [**Tensor Decompositions in Recursive Neural Networks for Tree-Structured Data**](https://github.com/danielecastellana22/tensor-tree-nn), ESANN'20, *Daniele Castellana, Davide Bacciu*
|
||||
|
||||
1. [**Combining Self-Organizing and Graph Neural Networks for Modeling Deformable Objects in Robotic Manipulation**](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7806087/), Frotiers in Robotics and AI, *Valencia, Angel J., and Pierre Payeur*
|
||||
|
||||
1. [**Joint stroke classification and text line grouping in online handwritten documents with edge pooling attention networks**](https://www.sciencedirect.com/science/article/abs/pii/S0031320321000467), Pattern Recognition, *Jun-Yu Ye, Yan-Ming Zhang, Qing Yang, Cheng-Lin Liu*
|
||||
|
||||
1. [**Toward Accurate Predictions of Atomic Properties via Quantum Mechanics Descriptors Augmented Graph Convolutional Neural Network: Application of This Novel Approach in NMR Chemical Shifts Predictions**](https://pubs.acs.org/doi/full/10.1021/acs.jpclett.0c02654), The Journal of Physical Chemistry Letters, *Peng Gao, Jie Zhang, Yuzhu Sun, and Jianguo Yu*
|
||||
|
||||
1. [**A Graph Neural Network to Model User Comfort in Robot Navigation**](https://arxiv.org/abs/2102.08863), *Pilar Bachiller, Daniel Rodriguez-Criado, Ronit R. Jorvekar, Pablo Bustos, Diego R. Faria, Luis J. Manso*
|
||||
|
||||
1. [**Medical Entity Disambiguation Using Graph Neural Networks**](https://arxiv.org/abs/2104.01488), *Alina Vretinaris, Chuan Lei, Vasilis Efthymiou, Xiao Qin, Fatma Özcan*
|
||||
|
||||
1. [**Chemistry-informed Macromolecule Graph Representation for Similarity Computation and Supervised Learning**](https://arxiv.org/abs/2103.02565), *Somesh Mohapatra, Joyce An, Rafael Gómez-Bombarelli*
|
||||
|
||||
1. [**Characterizing and Forecasting User Engagement with In-app Action Graph: A Case Study of Snapchat**](https://arxiv.org/pdf/1906.00355.pdf), *Yozen Liu, Xiaolin Shi, Lucas Pierce, Xiang Ren*
|
||||
|
||||
1. [**GIPA: General Information Propagation Algorithm for Graph Learning**](https://arxiv.org/abs/2105.06035), *Qinkai Zheng, Houyi Li, Peng Zhang, Zhixiong Yang, Guowei Zhang, Xintan Zeng, Yongchao Liu*
|
||||
|
||||
1. [**Graph Ensemble Learning over Multiple Dependency Trees for Aspect-level Sentiment Classification**](https://arxiv.org/abs/2103.11794), NAACL'21, *Xiaochen Hou, Peng Qi, Guangtao Wang, Rex Ying, Jing Huang, Xiaodong He, Bowen Zhou*
|
||||
|
||||
1. [**Enhancing Scientific Papers Summarization with Citation Graph**](https://arxiv.org/abs/2104.03057), AAAI'21, *Chenxin An, Ming Zhong, Yiran Chen, Danqing Wang, Xipeng Qiu, Xuanjing Huang*
|
||||
|
||||
1. [**Improving Graph Representation Learning by Contrastive Regularization**](https://arxiv.org/pdf/2101.11525.pdf), *Kaili Ma, Haochen Yang, Han Yang, Tatiana Jin, Pengfei Chen, Yongqiang Chen, Barakeel Fanseu Kamhoua, James Cheng*
|
||||
|
||||
1. [**Extract the Knowledge of Graph Neural Networks and Go Beyond it: An Effective Knowledge Distillation Framework**](https://arxiv.org/pdf/2103.02885.pdf), WWW'21, *Cheng Yang, Jiawei Liu, Chuan Shi*
|
||||
|
||||
1. [**VIKING: Adversarial Attack on Network Embeddings via Supervised Network Poisoning**](https://arxiv.org/pdf/2102.07164.pdf), PAKDD'21, *Viresh Gupta, Tanmoy Chakraborty*
|
||||
|
||||
1. [**Knowledge Graph Embedding using Graph Convolutional Networks with Relation-Aware Attention**](https://arxiv.org/pdf/2102.07200.pdf), *Nasrullah Sheikh, Xiao Qin, Berthold Reinwald, Christoph Miksovic, Thomas Gschwind, Paolo Scotton*
|
||||
|
||||
1. [**SLAPS: Self-Supervision Improves Structure Learning for Graph Neural Networks**](https://arxiv.org/pdf/2102.05034.pdf), *Bahare Fatemi, Layla El Asri, Seyed Mehran Kazemi*
|
||||
|
||||
1. [**Finding Needles in Heterogeneous Haystacks**](https://homepage.divms.uiowa.edu/~badhikari/assets/doc/papers/CONGCNIAAI2021.pdf), AAAI'21, *Bijaya Adhikari, Liangyue Li, Nikhil Rao, Karthik Subbian*
|
||||
|
||||
1. [**RetCL: A Selection-based Approach for Retrosynthesis via Contrastive Learning**](https://arxiv.org/abs/2105.00795), IJCAI 2021, *Hankook Lee, Sungsoo Ahn, Seung-Woo Seo, You Young Song, Eunho Yang, Sung-Ju Hwang, Jinwoo Shin*
|
||||
|
||||
1. [**Accurate Prediction of Free Solvation Energy of Organic Molecules via Graph Attention Network and Message Passing Neural Network from Pairwise Atomistic Interactions**](https://arxiv.org/abs/2105.02048), *Ramin Ansari, Amirata Ghorbani*
|
||||
|
||||
1. [**DIPS-Plus: The Enhanced Database of Interacting Protein Structures for Interface Prediction**](https://arxiv.org/abs/2106.04362), *Alex Morehead, Chen Chen, Ada Sedova, Jianlin Cheng*
|
||||
|
||||
1. [**Coreference-Aware Dialogue Summarization**](https://arxiv.org/abs/2106.08556), SIGDIAL'21, *Zhengyuan Liu, Ke Shi, Nancy F. Chen*
|
||||
|
||||
1. [**Document Structure aware Relational Graph Convolutional Networks for Ontology Population**](https://arxiv.org/abs/2104.12950), arXiv, *Abhay M Shalghar, Ayush Kumar, Balaji Ganesan, Aswin Kannan, Shobha G*
|
||||
|
||||
1. [**Covid-19 Detection from Chest X-ray and Patient Metadata using Graph Convolutional Neural Networks**](https://arxiv.org/abs/2105.09720), *Thosini Bamunu Mudiyanselage, Nipuna Senanayake, Chunyan Ji, Yi Pan, Yanqing Zhang*
|
||||
|
||||
1. [**Rossmann-toolbox: a deep learning-based protocol for the prediction and design of cofactor specificity in Rossmann fold proteins**](https://academic.oup.com/bib/advance-article/doi/10.1093/bib/bbab371/6375059), Briefings in Bioinformatics, *Kamil Kaminski, Jan Ludwiczak, Maciej Jasinski, Adriana Bukala, Rafal Madaj, Krzysztof Szczepaniak, Stanislaw Dunin-Horkawicz*
|
||||
|
||||
1. [**LGESQL: Line Graph Enhanced Text-to-SQL Model with Mixed Local and Non-Local Relations**](https://arxiv.org/pdf/2106.01093.pdf), ACL'21, *Ruisheng Cao, Lu Chen, Zhi Chen, Yanbin Zhao, Su Zhu, Kai Yu*
|
||||
|
||||
1. [**Enhancing Graph Neural Networks via auxiliary training for semi-supervised node classification**](https://www.sciencedirect.com/science/article/pii/S0950705121001477), Knowledge-Based System'21, *Yao Wu, Yu Song, Hong Huang, Fanghua Ye, Xing Xie, Hai Jin*
|
||||
|
||||
1. [**Modeling Graph Node Correlations with Neighbor Mixture Models**](https://arxiv.org/pdf/2103.15966.pdf), *Linfeng Liu, Michael C. Hughes, Li-Ping Liu*
|
||||
|
||||
1. [**COMBINING PHYSICS AND MACHINE LEARNING FOR NETWORK FLOW ESTIMATION**](https://openreview.net/pdf/9dc2744a465941220de07cf308acf822ec8aaa64.pdf), ICLR'21, *Arlei Silva, Furkan Kocayusufoglu, Saber Jafarpour, Francesco Bullo, Ananthram Swami, Ambuj Singh*
|
||||
|
||||
1. [**A Classification Method for Academic Resources Based on a Graph Attention Network**](https://www.mdpi.com/1999-5903/13/3/64/htm), Future Internet'21, *Jie Yu, Yaliu Li, Chenle Pan and Junwei Wang*
|
||||
|
||||
1. [**Large Graph Convolutional Network Training with GPU-Oriented Data Communication Architecture**](https://arxiv.org/abs/2103.03330), *Seung Won Min, Kun Wu, Sitao Huang, Mert Hidayetoğlu, Jinjun Xiong, Eiman Ebrahimi, Deming Chen, Wen-mei Hwu*
|
||||
|
||||
1. [**Graph Attention Multi-Layer Perception**](https://github.com/PKU-DAIR/GAMLP/blob/main/GAMLP.pdf), *Wentao Zhang, Ziqi Yin, Zeang Sheng, Wen Ouyang, Xiaosen Li, Yangyu Tao, Zhi Yang, Bin Cui*
|
||||
|
||||
1. [**GNNLens: A Visual Analytics Approach for Prediction Error Diagnosis of Graph Neural Networks**](https://arxiv.org/abs/2011.11048v5), *Zhihua Jin, Yong Wang, Qianwen Wang, Yao Ming, Tengfei Ma, Huamin Qu*
|
||||
|
||||
1. [**How Attentive are Graph Attention Networks?**](https://arxiv.org/pdf/2105.14491.pdf), *Shaked Brody, Uri Alon, Eran Yahav*, [code](https://github.com/tech-srl/how_attentive_are_gats)
|
||||
|
||||
1. [**SCENE: Reasoning about Traffic Scenes using Heterogeneous Graph Neural Networks**](https://arxiv.org/pdf/2301.03512.pdf), *Thomas Monninger\*, Julian Schmidt\*, Jan Rupprecht, David Raba, Julian Jordan, Daniel Frank, Steffen Staab, Klaus Dietmayer*, [code](https://github.com/schmidt-ju/scene), \*co-first authors
|
||||
|
||||
</details>
|
||||
|
||||
## Contributing
|
||||
|
||||
Please let us know if you encounter a bug or have any suggestions by [filing an issue](https://github.com/dmlc/dgl/issues).
|
||||
|
||||
We welcome all contributions from bug fixes to new features and extensions.
|
||||
|
||||
We expect all contributions discussed in the issue tracker and going through PRs. Please refer to our [contribution guide](https://docs.dgl.ai/contribute.html).
|
||||
|
||||
## Cite
|
||||
|
||||
If you use DGL in a scientific publication, we would appreciate citations to the following paper:
|
||||
```
|
||||
@article{wang2019dgl,
|
||||
title={Deep Graph Library: A Graph-Centric, Highly-Performant Package for Graph Neural Networks},
|
||||
author={Minjie Wang and Da Zheng and Zihao Ye and Quan Gan and Mufei Li and Xiang Song and Jinjing Zhou and Chao Ma and Lingfan Yu and Yu Gai and Tianjun Xiao and Tong He and George Karypis and Jinyang Li and Zheng Zhang},
|
||||
year={2019},
|
||||
journal={arXiv preprint arXiv:1909.01315}
|
||||
}
|
||||
```
|
||||
|
||||
## The Team
|
||||
|
||||
DGL is developed and maintained by [NYU, NYU Shanghai, AWS Shanghai AI Lab, and AWS MXNet Science Team](https://www.dgl.ai/pages/about.html).
|
||||
|
||||
## License
|
||||
|
||||
DGL uses Apache License 2.0.
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`dmlc/dgl`
|
||||
- 原始仓库:https://github.com/dmlc/dgl
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -0,0 +1,3 @@
|
||||
# DGL-LifeSci
|
||||
|
||||
DGL-LifeSci is moved [here](https://github.com/awslabs/dgl-lifesci).
|
||||
@@ -0,0 +1,2 @@
|
||||
html
|
||||
results
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
pipeline {
|
||||
triggers {
|
||||
issueCommentTrigger('@dgl-bot .*')
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
label 'linux-benchmark-node'
|
||||
image 'dgllib/dgl-ci-lint'
|
||||
alwaysPull true
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Regression Test') {
|
||||
steps {
|
||||
checkout scm
|
||||
script {
|
||||
def commentTriggerCause = currentBuild.getBuildCauses('org.jenkinsci.plugins.pipeline.github.trigger.IssueCommentCause')
|
||||
def prOpenTriggerCause = currentBuild.getBuildCauses('jenkins.branch.BranchEventCause')
|
||||
def realTriggerCause = currentBuild.getBuildCauses()
|
||||
echo("BUILD CAUSE: ${realTriggerCause.toString()}")
|
||||
|
||||
if (commentTriggerCause) {
|
||||
dir('benchmark_scripts_repo') {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/master']],
|
||||
userRemoteConfigs: [[credentialsId: 'github', url: 'https://github.com/dglai/DGL_scripts.git']]])
|
||||
}
|
||||
sh('cp benchmark_scripts_repo/benchmark/* benchmarks/scripts/')
|
||||
def comment = env.GITHUB_COMMENT
|
||||
def author = env.GITHUB_COMMENT_AUTHOR
|
||||
def authorized_user = ['VoVAllen', 'BarclayII', 'jermainewang', 'zheng-da', 'mufeili']
|
||||
def isauthorized = author in authorized_user
|
||||
def command_lists = comment.split(' ')
|
||||
def instance_type = command_lists[2].replace('.', "")
|
||||
if (!isauthorized) {
|
||||
error("Not authorized to launch regression tests")
|
||||
}
|
||||
if (command_lists.size() != 5) {
|
||||
pullRequest.comment('Cannot run the regression test due to unknown command')
|
||||
error('Unknown command')
|
||||
} else {
|
||||
pullRequest.comment("Start the Regression test. View at ${RUN_DISPLAY_URL}")
|
||||
}
|
||||
dir('benchmarks/scripts') {
|
||||
sh('python3 -m pip install boto3')
|
||||
sh("PYTHONUNBUFFERED=1 GIT_URL=${env.GIT_URL} GIT_BRANCH=${env.CHANGE_BRANCH} python3 run_reg_test.py --data-folder ${env.GIT_COMMIT}_${instance_type} --run-cmd '${comment}'")
|
||||
}
|
||||
pullRequest.comment("Finished the Regression test. Result table is at https://dgl-asv-data.s3-us-west-2.amazonaws.com/${env.GIT_COMMIT}_${instance_type}/results/result.csv. Jenkins job link is ${RUN_DISPLAY_URL}. ")
|
||||
} else {
|
||||
// if (prOpenTriggerCause) {
|
||||
// if (env.BUILD_ID == "1") {
|
||||
// pullRequest.comment('To trigger regression tests: \n - `@dgl-bot run [instance-type] [which tests] [compare-with-branch]`; \n For example: `@dgl-bot run g4dn.4xlarge all dmlc/master` or `@dgl-bot run c5.9xlarge kernel,api dmlc/master`')
|
||||
// }
|
||||
// }
|
||||
echo('Build was not started by a trigger')
|
||||
}
|
||||
// echo("Comment: ${commentTriggerCause.getComment()}")
|
||||
}
|
||||
}
|
||||
post {
|
||||
failure {
|
||||
echo '========Regression execution failed========'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
DGL Benchmarks
|
||||
====
|
||||
|
||||
Benchmarking DGL with Airspeed Velocity.
|
||||
|
||||
Usage
|
||||
---
|
||||
|
||||
Before beginning, ensure that airspeed velocity is installed:
|
||||
|
||||
```bash
|
||||
pip install asv
|
||||
```
|
||||
|
||||
To run all benchmarks locally, build the project first and then run:
|
||||
|
||||
```bash
|
||||
asv run -n -e --python=same --verbose
|
||||
```
|
||||
|
||||
**Due to ASV's restriction, `--python=same` will not write any benchmark results
|
||||
to disk. It does not support specifying branches and commits either. They are only
|
||||
available under ASV's managed environment.**
|
||||
|
||||
To change the device for benchmarking, set the `DGL_BENCH_DEVICE` environment variable.
|
||||
Allowed values are `"cpu"` or `"gpu"`.
|
||||
|
||||
```bash
|
||||
export DGL_BENCH_DEVICE=gpu
|
||||
```
|
||||
|
||||
To select which benchmark to run, use the `--bench` flag. For example,
|
||||
|
||||
```bash
|
||||
asv run -n -e --python=same --verbose --bench model_acc.bench_gat
|
||||
```
|
||||
|
||||
Note that OGB dataset need to be download manually to `/tmp/dataset` folder (i.e. `/tmp/dataset/ogbn-products/`) beforehand.
|
||||
You can do it by runnnig the code below in this folder
|
||||
```python
|
||||
from benchmarks.utils import get_ogb_graph
|
||||
get_ogb_graph("ogbn-product")
|
||||
```
|
||||
|
||||
Run in docker locally
|
||||
---
|
||||
|
||||
DGL runs all benchmarks automatically in docker container. To run bencmarks in docker locally,
|
||||
|
||||
* Git commit your locally changes. No need to push to remote repository.
|
||||
* To compare commits from different branches. Change the `"branches"` list in `asv.conf.json`.
|
||||
The default is `"HEAD"` which is the last commit of the current branch. For example, to
|
||||
compare your proposed changes with the master branch, set it to be `["HEAD", "master"]`.
|
||||
If your workspace is a forked repository, make sure your local master has synced with
|
||||
the upstream.
|
||||
* Use the `publish.sh` script. It accepts two arguments, a name specifying the identity of
|
||||
the test machine and a device name. For example,
|
||||
```bash
|
||||
bash publish.sh dev-machine gpu
|
||||
```
|
||||
|
||||
The script will output two folders `results` and `html`. The `html` folder contains the
|
||||
generated static web pages. View it by:
|
||||
|
||||
```bash
|
||||
asv preview
|
||||
```
|
||||
|
||||
Please see `publish.sh` for more information on how it works and how to modify it according
|
||||
to your need.
|
||||
|
||||
Adding a new benchmark suite
|
||||
---
|
||||
|
||||
The benchmark folder is organized as follows:
|
||||
|
||||
```
|
||||
|-- benchmarks/
|
||||
|-- model_acc/ # benchmarks for model accuracy
|
||||
|-- bench_gcn.py
|
||||
|-- bench_gat.py
|
||||
|-- bench_sage.py
|
||||
...
|
||||
|-- model_speed/ # benchmarks for model training speed
|
||||
|-- bench_gat.py
|
||||
|-- bench_sage.py
|
||||
...
|
||||
... # other types of benchmarks
|
||||
|-- html/ # generated html files
|
||||
|-- results/ # generated result files
|
||||
|-- asv.conf.json # asv config file
|
||||
|-- build_dgl_asv.sh # script for building dgl in asv
|
||||
|-- install_dgl_asv.sh # script for installing dgl in asv
|
||||
|-- publish.sh # script for running benchmarks in docker
|
||||
|-- README.md # this readme
|
||||
|-- run.sh # script for calling asv in docker
|
||||
|-- ... # other aux files
|
||||
```
|
||||
|
||||
To add a new benchmark, pick a suitable benchmark type and create a python script under
|
||||
it. We prefer to have the prefix `bench_` in the name. Here is a toy example:
|
||||
|
||||
```python
|
||||
# bench_range.py
|
||||
|
||||
import time
|
||||
from .. import utils
|
||||
|
||||
@utils.benchmark('time')
|
||||
@utils.parametrize('l', [10, 100, 1000])
|
||||
@utils.parametrize('u', [10, 100, 1000])
|
||||
def track_time(l, u):
|
||||
t0 = time.time()
|
||||
for i in range(l, u):
|
||||
pass
|
||||
return time.time() - t0
|
||||
```
|
||||
|
||||
* The main entry point of each benchmark script is a `track_*` function. The function
|
||||
can have arbitrary arguments and must return the benchmark result.
|
||||
* There are two useful decorators: `utils.benchmark` and `utils.parametrize`.
|
||||
* `utils.benchmark` indicates the type of this benchmark. Currently supported types are:
|
||||
`'time'` and `'acc'`. The decorator will perform some necessary setup and finalize
|
||||
steps such as fixing the random seed for the `'acc'` type.
|
||||
* `utils.parametrize` specifies the parameters to test.
|
||||
Multiple parametrize decorators mean benchmarking the combination.
|
||||
* Check out `model_acc/bench_gcn.py` and `model_speed/bench_sage.py`.
|
||||
* ASV's [official guide on writing benchmarks](https://asv.readthedocs.io/en/stable/writing_benchmarks.html)
|
||||
is also very helpful.
|
||||
|
||||
|
||||
Tips
|
||||
----
|
||||
* Feed flags `-e --verbose` to `asv run` to print out stderr and more information.
|
||||
* When running benchmarks locally (e.g., with `--python=same`), ASV will not write results to disk
|
||||
so `asv publish` will not generate plots.
|
||||
* Try make your benchmarks compatible with all the versions being tested.
|
||||
* For ogbn dataset, put the dataset into /tmp/dataset/
|
||||
@@ -0,0 +1,142 @@
|
||||
{
|
||||
// The version of the config file format. Do not change, unless
|
||||
// you know what you are doing.
|
||||
"version": 1,
|
||||
// The name of the project being benchmarked
|
||||
"project": "dgl",
|
||||
// The project's homepage
|
||||
"project_url": "https://www.dgl.ai",
|
||||
// The URL or local path of the source code repository for the
|
||||
// project being benchmarked
|
||||
"repo": "..",
|
||||
// The Python project's subdirectory in your repo. If missing or
|
||||
// the empty string, the project is assumed to be located at the root
|
||||
// of the repository.
|
||||
// "repo_subdir": "python",
|
||||
// Customizable commands for building, installing, and
|
||||
// uninstalling the project. See asv.conf.json documentation.
|
||||
//
|
||||
"build_command": [
|
||||
"/bin/bash {conf_dir}/scripts/build_dgl_asv.sh"
|
||||
],
|
||||
"install_command": [
|
||||
"/bin/bash {conf_dir}/scripts/install_dgl_asv.sh"
|
||||
],
|
||||
"uninstall_command": [
|
||||
"return-code=any python -m pip uninstall -y dgl"
|
||||
],
|
||||
// List of branches to benchmark. If not provided, defaults to "master"
|
||||
// (for git) or "default" (for mercurial).
|
||||
"branches": [
|
||||
"HEAD"
|
||||
], // for git
|
||||
// The DVCS being used. If not set, it will be automatically
|
||||
// determined from "repo" by looking at the protocol in the URL
|
||||
// (if remote), or by looking for special directories, such as
|
||||
// ".git" (if local).
|
||||
"dvcs": "git",
|
||||
// The tool to use to create environments. May be "conda",
|
||||
// "virtualenv" or other value depending on the plugins in use.
|
||||
// If missing or the empty string, the tool will be automatically
|
||||
// determined by looking for tools on the PATH environment
|
||||
// variable.
|
||||
"environment_type": "conda",
|
||||
// timeout in seconds for installing any dependencies in environment
|
||||
// defaults to 10 min
|
||||
"install_timeout": 600,
|
||||
// the base URL to show a commit for the project.
|
||||
// "show_commit_url": "http://github.com/owner/project/commit/",
|
||||
// The Pythons you'd like to test against. If not provided, defaults
|
||||
// to the current version of Python used to run `asv`.
|
||||
// "pythons": ["2.7", "3.6"],
|
||||
// The list of conda channel names to be searched for benchmark
|
||||
// dependency packages in the specified order
|
||||
// "conda_channels": ["conda-forge", "defaults"],
|
||||
// The matrix of dependencies to test. Each key is the name of a
|
||||
// package (in PyPI) and the values are version numbers. An empty
|
||||
// list or empty string indicates to just test against the default
|
||||
// (latest) version. null indicates that the package is to not be
|
||||
// installed. If the package to be tested is only available from
|
||||
// PyPi, and the 'environment_type' is conda, then you can preface
|
||||
// the package name by 'pip+', and the package will be installed via
|
||||
// pip (with all the conda available packages installed first,
|
||||
// followed by the pip installed packages).
|
||||
//
|
||||
// "matrix": {
|
||||
// "numpy": ["1.6", "1.7"],
|
||||
// "six": ["", null], // test with and without six installed
|
||||
// "pip+emcee": [""], // emcee is only available for install with pip.
|
||||
// },
|
||||
// Combinations of libraries/python versions can be excluded/included
|
||||
// from the set to test. Each entry is a dictionary containing additional
|
||||
// key-value pairs to include/exclude.
|
||||
//
|
||||
// An exclude entry excludes entries where all values match. The
|
||||
// values are regexps that should match the whole string.
|
||||
//
|
||||
// An include entry adds an environment. Only the packages listed
|
||||
// are installed. The 'python' key is required. The exclude rules
|
||||
// do not apply to includes.
|
||||
//
|
||||
// In addition to package names, the following keys are available:
|
||||
//
|
||||
// - python
|
||||
// Python version, as in the *pythons* variable above.
|
||||
// - environment_type
|
||||
// Environment type, as above.
|
||||
// - sys_platform
|
||||
// Platform, as in sys.platform. Possible values for the common
|
||||
// cases: 'linux2', 'win32', 'cygwin', 'darwin'.
|
||||
//
|
||||
// "exclude": [
|
||||
// {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows
|
||||
// {"environment_type": "conda", "six": null}, // don't run without six on conda
|
||||
// ],
|
||||
//
|
||||
// "include": [
|
||||
// // additional env for python2.7
|
||||
// {"python": "2.7", "numpy": "1.8"},
|
||||
// // additional env if run on windows+conda
|
||||
// {"platform": "win32", "environment_type": "conda", "python": "2.7", "libpython": ""},
|
||||
// ],
|
||||
// The directory (relative to the current directory) that benchmarks are
|
||||
// stored in. If not provided, defaults to "benchmarks"
|
||||
// "benchmark_dir": "benchmarks",
|
||||
// The directory (relative to the current directory) to cache the Python
|
||||
// environments in. If not provided, defaults to "env"
|
||||
"env_dir": "env",
|
||||
// The directory (relative to the current directory) that raw benchmark
|
||||
// results are stored in. If not provided, defaults to "results".
|
||||
"results_dir": "results",
|
||||
// The directory (relative to the current directory) that the html tree
|
||||
// should be written to. If not provided, defaults to "html".
|
||||
"html_dir": "html",
|
||||
// The number of characters to retain in the commit hashes.
|
||||
// "hash_length": 8,
|
||||
// `asv` will cache results of the recent builds in each
|
||||
// environment, making them faster to install next time. This is
|
||||
// the number of builds to keep, per environment.
|
||||
// "build_cache_size": 2,
|
||||
// The commits after which the regression search in `asv publish`
|
||||
// should start looking for regressions. Dictionary whose keys are
|
||||
// regexps matching to benchmark names, and values corresponding to
|
||||
// the commit (exclusive) after which to start looking for
|
||||
// regressions. The default is to start from the first commit
|
||||
// with results. If the commit is `null`, regression detection is
|
||||
// skipped for the matching benchmark.
|
||||
//
|
||||
// "regressions_first_commits": {
|
||||
// "some_benchmark": "352cdf", // Consider regressions only after this commit
|
||||
// "another_benchmark": null, // Skip regression detection altogether
|
||||
// },
|
||||
// The thresholds for relative change in results, after which `asv
|
||||
// publish` starts reporting regressions. Dictionary of the same
|
||||
// form as in ``regressions_first_commits``, with values
|
||||
// indicating the thresholds. If multiple entries match, the
|
||||
// maximum is taken. If no entry matches, the default is 5%.
|
||||
//
|
||||
// "regressions_thresholds": {
|
||||
// "some_benchmark": 0.01, // Threshold of 1%
|
||||
// "another_benchmark": 0.5, // Threshold of 50%
|
||||
// },
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("graph_name", ["cora", "livejournal"])
|
||||
@utils.parametrize("format", ["coo"])
|
||||
def track_time(graph_name, format):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
g = graph.add_self_loop()
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
edges = graph.add_self_loop()
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,31 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("batch_size", [4, 32, 256, 1024])
|
||||
def track_time(batch_size):
|
||||
device = utils.get_bench_device()
|
||||
ds = dgl.data.QM7bDataset()
|
||||
# prepare graph
|
||||
graphs = []
|
||||
for graph in ds[0:batch_size][0]:
|
||||
g = graph.to(device)
|
||||
graphs.append(g)
|
||||
|
||||
# dry run
|
||||
for i in range(10):
|
||||
g = dgl.batch(graphs)
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(100):
|
||||
g = dgl.batch(graphs)
|
||||
|
||||
return t.elapsed_secs / 100
|
||||
@@ -0,0 +1,40 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("graph_name", ["cora", "ogbn-arxiv"])
|
||||
@utils.parametrize("format", ["coo", "csr"])
|
||||
@utils.parametrize("feat_size", [8, 128, 512])
|
||||
@utils.parametrize("reduce_type", ["u->e", "u+v"])
|
||||
def track_time(graph_name, format, feat_size, reduce_type):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
graph.ndata["h"] = torch.randn(
|
||||
(graph.num_nodes(), feat_size), device=device
|
||||
)
|
||||
|
||||
reduce_builtin_dict = {
|
||||
"u->e": fn.copy_u("h", "x"),
|
||||
"u+v": fn.u_add_v("h", "h", "x"),
|
||||
}
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
graph.apply_edges(reduce_builtin_dict[reduce_type])
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
graph.apply_edges(reduce_builtin_dict[reduce_type])
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,54 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("num_relations", [5, 50, 500])
|
||||
@utils.parametrize("format", ["coo", "csr"])
|
||||
@utils.parametrize("feat_size", [8, 128, 512])
|
||||
@utils.parametrize("reduce_type", ["u->e"]) # , 'e->u'])
|
||||
def track_time(num_relations, format, feat_size, reduce_type):
|
||||
device = utils.get_bench_device()
|
||||
dd = {}
|
||||
candidate_edges = [
|
||||
dgl.data.CoraGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.PubmedGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.CiteseerGraphDataset(verbose=False)[0].edges(),
|
||||
]
|
||||
for i in range(num_relations):
|
||||
dd[("n1", "e_{}".format(i), "n2")] = candidate_edges[
|
||||
i % len(candidate_edges)
|
||||
]
|
||||
graph = dgl.heterograph(dd)
|
||||
|
||||
graph = graph.to(device)
|
||||
graph.nodes["n1"].data["h"] = torch.randn(
|
||||
(graph.num_nodes("n1"), feat_size), device=device
|
||||
)
|
||||
graph.nodes["n2"].data["h"] = torch.randn(
|
||||
(graph.num_nodes("n2"), feat_size), device=device
|
||||
)
|
||||
|
||||
reduce_builtin_dict = {
|
||||
"u->e": fn.copy_u("h", "x"),
|
||||
# 'e->u': fn.copy_e('h', 'x'),
|
||||
}
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
graph.apply_edges(reduce_builtin_dict[reduce_type])
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
graph.apply_edges(reduce_builtin_dict[reduce_type])
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,49 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("feat_size", [32, 128, 512])
|
||||
@utils.parametrize("num_relations", [5, 50, 500])
|
||||
@utils.parametrize("multi_reduce_type", ["sum", "stack"])
|
||||
def track_time(feat_size, num_relations, multi_reduce_type):
|
||||
device = utils.get_bench_device()
|
||||
dd = {}
|
||||
candidate_edges = [
|
||||
dgl.data.CoraGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.PubmedGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.CiteseerGraphDataset(verbose=False)[0].edges(),
|
||||
]
|
||||
for i in range(num_relations):
|
||||
dd[("n1", "e_{}".format(i), "n2")] = candidate_edges[
|
||||
i % len(candidate_edges)
|
||||
]
|
||||
graph = dgl.heterograph(dd)
|
||||
|
||||
graph = graph.to(device)
|
||||
graph.nodes["n1"].data["h"] = torch.randn(
|
||||
(graph.num_nodes("n1"), feat_size), device=device
|
||||
)
|
||||
graph.nodes["n2"].data["h"] = torch.randn(
|
||||
(graph.num_nodes("n2"), feat_size), device=device
|
||||
)
|
||||
|
||||
# dry run
|
||||
update_dict = {}
|
||||
for i in range(num_relations):
|
||||
update_dict["e_{}".format(i)] = (fn.copy_u("h", "m"), fn.sum("m", "h"))
|
||||
graph.multi_update_all(update_dict, multi_reduce_type)
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
graph.multi_update_all(update_dict, multi_reduce_type)
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,51 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("graph_name", ["ogbn-arxiv"])
|
||||
@utils.parametrize("format", ["coo"])
|
||||
@utils.parametrize("feat_size", [4, 32, 256])
|
||||
@utils.parametrize("msg_type", ["copy_u", "u_mul_e"])
|
||||
@utils.parametrize("reduce_type", ["sum", "mean", "max"])
|
||||
def track_time(graph_name, format, feat_size, msg_type, reduce_type):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
graph.ndata["h"] = torch.randn(
|
||||
(graph.num_nodes(), feat_size), device=device
|
||||
)
|
||||
graph.edata["e"] = torch.randn((graph.num_edges(), 1), device=device)
|
||||
|
||||
msg_builtin_dict = {
|
||||
"copy_u": fn.copy_u("h", "x"),
|
||||
"u_mul_e": fn.u_mul_e("h", "e", "x"),
|
||||
}
|
||||
|
||||
reduce_builtin_dict = {
|
||||
"sum": fn.sum("x", "h_new"),
|
||||
"mean": fn.mean("x", "h_new"),
|
||||
"max": fn.max("x", "h_new"),
|
||||
}
|
||||
|
||||
# dry run
|
||||
graph.update_all(
|
||||
msg_builtin_dict[msg_type], reduce_builtin_dict[reduce_type]
|
||||
)
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
graph.update_all(
|
||||
msg_builtin_dict[msg_type], reduce_builtin_dict[reduce_type]
|
||||
)
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,53 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("graph_name", ["ogbn-arxiv", "reddit", "ogbn-proteins"])
|
||||
@utils.parametrize("format", ["csc"])
|
||||
@utils.parametrize("feat_size", [4, 32, 256])
|
||||
@utils.parametrize("msg_type", ["copy_u", "u_mul_e"])
|
||||
@utils.parametrize("reduce_type", ["sum", "mean", "max"])
|
||||
def track_time(graph_name, format, feat_size, msg_type, reduce_type):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
graph.ndata["h"] = torch.randn(
|
||||
(graph.num_nodes(), feat_size), device=device
|
||||
)
|
||||
graph.edata["e"] = torch.randn((graph.num_edges(), 1), device=device)
|
||||
|
||||
msg_builtin_dict = {
|
||||
"copy_u": fn.copy_u("h", "x"),
|
||||
"u_mul_e": fn.u_mul_e("h", "e", "x"),
|
||||
}
|
||||
|
||||
reduce_builtin_dict = {
|
||||
"sum": fn.sum("x", "h_new"),
|
||||
"mean": fn.mean("x", "h_new"),
|
||||
"max": fn.max("x", "h_new"),
|
||||
}
|
||||
|
||||
# dry run
|
||||
|
||||
for i in range(3):
|
||||
graph.update_all(
|
||||
msg_builtin_dict[msg_type], reduce_builtin_dict[reduce_type]
|
||||
)
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
graph.update_all(
|
||||
msg_builtin_dict[msg_type], reduce_builtin_dict[reduce_type]
|
||||
)
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,43 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
# edge_ids is not supported on cuda
|
||||
# @utils.skip_if_gpu()
|
||||
@utils.benchmark("time", timeout=1200)
|
||||
@utils.parametrize_cpu("graph_name", ["cora", "livejournal", "friendster"])
|
||||
@utils.parametrize_gpu("graph_name", ["cora", "livejournal"])
|
||||
@utils.parametrize("format", ["coo", "csr", "csc"])
|
||||
@utils.parametrize("fraction", [0.01, 0.1])
|
||||
@utils.parametrize("return_uv", [True, False])
|
||||
def track_time(graph_name, format, fraction, return_uv):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
coo_graph = utils.get_graph(graph_name, "coo")
|
||||
graph = graph.to(device)
|
||||
eids = np.random.choice(
|
||||
np.arange(graph.num_edges(), dtype=np.int64),
|
||||
int(graph.num_edges() * fraction),
|
||||
)
|
||||
eids = torch.tensor(eids, device="cpu", dtype=torch.int64)
|
||||
u, v = coo_graph.find_edges(eids)
|
||||
del coo_graph, eids
|
||||
u = u.to(device)
|
||||
v = v.to(device)
|
||||
# dry run
|
||||
for i in range(10):
|
||||
out = graph.edge_ids(u[0], v[0])
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
edges = graph.edge_ids(u, v, return_uv=return_uv)
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,34 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("graph_name", ["livejournal", "reddit"])
|
||||
@utils.parametrize("format", ["coo"])
|
||||
@utils.parametrize("seed_egdes_num", [500, 5000, 50000])
|
||||
def track_time(graph_name, format, seed_egdes_num):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
|
||||
seed_edges = np.random.randint(0, graph.num_edges(), seed_egdes_num)
|
||||
seed_edges = torch.from_numpy(seed_edges).to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
dgl.edge_subgraph(graph, seed_edges)
|
||||
|
||||
# timing
|
||||
num_iters = 50
|
||||
with utils.Timer() as t:
|
||||
for i in range(num_iters):
|
||||
dgl.edge_subgraph(graph, seed_edges)
|
||||
|
||||
return t.elapsed_secs / num_iters
|
||||
@@ -0,0 +1,38 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize_cpu("graph_name", ["cora", "livejournal", "friendster"])
|
||||
@utils.parametrize_gpu("graph_name", ["cora", "livejournal"])
|
||||
@utils.parametrize("format", ["coo"]) # csc is not supported
|
||||
@utils.parametrize("fraction", [0.01, 0.1])
|
||||
def track_time(graph_name, format, fraction):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
eids = np.random.choice(
|
||||
np.arange(graph.num_edges(), dtype=np.int64),
|
||||
int(graph.num_edges() * fraction),
|
||||
)
|
||||
eids = torch.tensor(eids, device=device, dtype=torch.int64)
|
||||
# dry run
|
||||
for i in range(10):
|
||||
out = graph.find_edges(i)
|
||||
out = graph.find_edges(
|
||||
torch.arange(i * 10, dtype=torch.int64, device=device)
|
||||
)
|
||||
|
||||
# timing
|
||||
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
edges = graph.find_edges(eids)
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,45 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize_cpu(
|
||||
"graph_name", ["cora", "pubmed", "ogbn-arxiv", "livejournal", "friendster"]
|
||||
)
|
||||
@utils.parametrize_gpu("graph_name", ["cora", "livejournal"])
|
||||
@utils.parametrize(
|
||||
"format",
|
||||
[
|
||||
("coo", "csc"),
|
||||
("csc", "coo"),
|
||||
("coo", "csr"),
|
||||
("csr", "coo"),
|
||||
("csr", "csc"),
|
||||
("csc", "csr"),
|
||||
],
|
||||
)
|
||||
def track_time(graph_name, format):
|
||||
from_format, to_format = format
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, from_format)
|
||||
graph = graph.to(device)
|
||||
if format == ("coo", "csr") and graph_name == "friendster":
|
||||
# Mark graph as sorted to check performance for COO matrix marked as
|
||||
# sorted. Note that friendster dataset is already sorted.
|
||||
graph = dgl.graph(graph.edges(), row_sorted=True)
|
||||
graph = graph.formats([from_format])
|
||||
# dry run
|
||||
graph.formats([to_format])
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
gg = graph.formats([to_format])
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,39 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize_cpu("graph_name", ["livejournal", "reddit"])
|
||||
@utils.parametrize_gpu("graph_name", ["ogbn-arxiv", "reddit"])
|
||||
@utils.parametrize("format", ["csr", "csc"])
|
||||
@utils.parametrize("seed_nodes_num", [200, 5000, 20000])
|
||||
@utils.parametrize("fanout", [5, 20, 40])
|
||||
def track_time(graph_name, format, seed_nodes_num, fanout):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format).to(device)
|
||||
|
||||
edge_dir = "in" if format == "csc" else "out"
|
||||
seed_nodes = np.random.randint(0, graph.num_nodes(), seed_nodes_num)
|
||||
seed_nodes = torch.from_numpy(seed_nodes).to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
dgl.sampling.sample_neighbors_fused(
|
||||
graph, seed_nodes, fanout, edge_dir=edge_dir
|
||||
)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(50):
|
||||
dgl.sampling.sample_neighbors_fused(
|
||||
graph, seed_nodes, fanout, edge_dir=edge_dir
|
||||
)
|
||||
|
||||
return t.elapsed_secs / 50
|
||||
@@ -0,0 +1,34 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("num_relations", [5, 50, 500])
|
||||
def track_time(num_relations):
|
||||
dd = {}
|
||||
candidate_edges = [
|
||||
dgl.data.CoraGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.PubmedGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.CiteseerGraphDataset(verbose=False)[0].edges(),
|
||||
]
|
||||
for i in range(num_relations):
|
||||
dd[("n1", "e_{}".format(i), "n2")] = candidate_edges[
|
||||
i % len(candidate_edges)
|
||||
]
|
||||
|
||||
# dry run
|
||||
graph = dgl.heterograph(dd)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
graph = dgl.heterograph(dd)
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,29 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.skip_if_gpu()
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("size", ["small", "large"])
|
||||
def track_time(size):
|
||||
edge_list = {
|
||||
"small": dgl.data.CiteseerGraphDataset(verbose=False)[0].edges(),
|
||||
"large": utils.get_livejournal().edges(),
|
||||
}
|
||||
|
||||
# dry run
|
||||
dgl.graph(edge_list[size])
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
g = dgl.graph(edge_list[size])
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,32 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.skip_if_gpu()
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("size", ["small", "large"])
|
||||
@utils.parametrize("scipy_format", ["coo", "csr"])
|
||||
def track_time(size, scipy_format):
|
||||
matrix_dict = {
|
||||
"small": dgl.data.CiteseerGraphDataset(verbose=False)[0].adj_external(
|
||||
scipy_fmt=scipy_format
|
||||
),
|
||||
"large": utils.get_livejournal().adj_external(scipy_fmt=scipy_format),
|
||||
}
|
||||
|
||||
# dry run
|
||||
dgl.from_scipy(matrix_dict[size])
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
dgl.from_scipy(matrix_dict[size])
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,37 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=1200)
|
||||
@utils.parametrize_cpu("graph_name", ["cora", "livejournal", "friendster"])
|
||||
@utils.parametrize_gpu("graph_name", ["cora", "livejournal"])
|
||||
# in_degrees on coo is not supported on cuda
|
||||
@utils.parametrize_cpu("format", ["coo", "csc"])
|
||||
@utils.parametrize_gpu("format", ["csc"])
|
||||
@utils.parametrize("fraction", [0.01, 0.1])
|
||||
def track_time(graph_name, format, fraction):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
nids = np.random.choice(
|
||||
np.arange(graph.num_nodes(), dtype=np.int64),
|
||||
int(graph.num_nodes() * fraction),
|
||||
)
|
||||
nids = torch.tensor(nids, device=device, dtype=torch.int64)
|
||||
|
||||
# dry run
|
||||
for i in range(10):
|
||||
out = graph.in_degrees(i)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
edges = graph.in_degrees(nids)
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,38 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=1200)
|
||||
@utils.parametrize_cpu("graph_name", ["cora", "livejournal", "friendster"])
|
||||
@utils.parametrize_gpu("graph_name", ["cora", "livejournal"])
|
||||
# in_edges on coo is not supported on cuda
|
||||
@utils.parametrize_cpu("format", ["coo", "csc"])
|
||||
@utils.parametrize_gpu("format", ["csc"])
|
||||
@utils.parametrize("fraction", [0.01, 0.1])
|
||||
def track_time(graph_name, format, fraction):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
|
||||
graph = graph.to(device)
|
||||
nids = np.random.choice(
|
||||
np.arange(graph.num_nodes(), dtype=np.int64),
|
||||
int(graph.num_nodes() * fraction),
|
||||
)
|
||||
nids = torch.tensor(nids, device=device, dtype=torch.int64)
|
||||
|
||||
# dry run
|
||||
for i in range(10):
|
||||
out = graph.in_edges(i)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
edges = graph.in_edges(nids)
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,34 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("graph_name", ["livejournal", "reddit"])
|
||||
@utils.parametrize("format", ["csc"]) # coo is not supported
|
||||
@utils.parametrize("seed_nodes_num", [200, 5000, 20000])
|
||||
def track_time(graph_name, format, seed_nodes_num):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
|
||||
seed_nodes = np.random.randint(0, graph.num_nodes(), seed_nodes_num)
|
||||
seed_nodes = torch.from_numpy(seed_nodes).to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
dgl.in_subgraph(graph, seed_nodes)
|
||||
|
||||
# timing
|
||||
num_iters = 50
|
||||
with utils.Timer() as t:
|
||||
for i in range(num_iters):
|
||||
dgl.in_subgraph(graph, seed_nodes)
|
||||
|
||||
return t.elapsed_secs / num_iters
|
||||
@@ -0,0 +1,28 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=60)
|
||||
@utils.parametrize("graph_name", ["cora"])
|
||||
@utils.parametrize("format", ["coo", "csr"])
|
||||
@utils.parametrize("k", [1, 3, 5])
|
||||
def track_time(graph_name, format, k):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
graph = graph.formats([format])
|
||||
# dry run
|
||||
dgl.khop_graph(graph, k)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
gg = dgl.khop_graph(graph, k)
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,34 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=60)
|
||||
@utils.parametrize("k", [8, 64])
|
||||
@utils.parametrize("size", [1000, 10000])
|
||||
@utils.parametrize("dim", [4, 32, 256])
|
||||
@utils.parametrize_cpu(
|
||||
"algorithm", ["bruteforce-blas", "bruteforce", "kd-tree", "nn-descent"]
|
||||
)
|
||||
@utils.parametrize_gpu(
|
||||
"algorithm",
|
||||
["bruteforce-blas", "bruteforce", "bruteforce-sharemem", "nn-descent"],
|
||||
)
|
||||
def track_time(size, dim, k, algorithm):
|
||||
device = utils.get_bench_device()
|
||||
features = np.random.RandomState(42).randn(size, dim)
|
||||
feat = torch.tensor(features, dtype=torch.float, device=device)
|
||||
# dry run
|
||||
for i in range(1):
|
||||
dgl.knn_graph(feat, k, algorithm=algorithm)
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(5):
|
||||
dgl.knn_graph(feat, k, algorithm=algorithm)
|
||||
|
||||
return t.elapsed_secs / 5
|
||||
@@ -0,0 +1,27 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.skip_if_gpu()
|
||||
@utils.benchmark("time", timeout=1200)
|
||||
@utils.parametrize("graph_name", ["reddit"])
|
||||
@utils.parametrize("k", [2, 4, 8])
|
||||
def track_time(graph_name, k):
|
||||
device = utils.get_bench_device()
|
||||
data = utils.process_data(graph_name)
|
||||
graph = data[0]
|
||||
# dry run
|
||||
gg = dgl.transforms.metis_partition(graph, k)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
gg = dgl.transforms.metis_partition(graph, k)
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,36 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn.pytorch import SAGEConv
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("graph_name", ["pubmed", "ogbn-arxiv"])
|
||||
@utils.parametrize("feat_dim", [4, 32, 256])
|
||||
@utils.parametrize("aggr_type", ["mean", "gcn", "pool"])
|
||||
def track_time(graph_name, feat_dim, aggr_type):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name).to(device)
|
||||
|
||||
feat = torch.randn((graph.num_nodes(), feat_dim), device=device)
|
||||
model = SAGEConv(
|
||||
feat_dim, feat_dim, aggr_type, activation=F.relu, bias=False
|
||||
).to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
model(graph, feat)
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(50):
|
||||
model(graph, feat)
|
||||
|
||||
return t.elapsed_secs / 50
|
||||
@@ -0,0 +1,54 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn.pytorch import HeteroGraphConv, SAGEConv
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("feat_dim", [4, 32, 256])
|
||||
@utils.parametrize("num_relations", [5, 50, 200])
|
||||
def track_time(feat_dim, num_relations):
|
||||
device = utils.get_bench_device()
|
||||
dd = {}
|
||||
nn_dict = {}
|
||||
candidate_edges = [
|
||||
dgl.data.CoraGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.PubmedGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.CiteseerGraphDataset(verbose=False)[0].edges(),
|
||||
]
|
||||
for i in range(num_relations):
|
||||
dd[("n1", "e_{}".format(i), "n2")] = candidate_edges[
|
||||
i % len(candidate_edges)
|
||||
]
|
||||
nn_dict["e_{}".format(i)] = SAGEConv(
|
||||
feat_dim, feat_dim, "mean", activation=F.relu
|
||||
)
|
||||
|
||||
# dry run
|
||||
feat_dict = {}
|
||||
graph = dgl.heterograph(dd)
|
||||
for i in range(num_relations):
|
||||
etype = "e_{}".format(i)
|
||||
feat_dict[etype] = torch.randn(
|
||||
(graph[etype].num_nodes(), feat_dim), device=device
|
||||
)
|
||||
|
||||
conv = HeteroGraphConv(nn_dict).to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
conv(graph, feat_dict)
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(50):
|
||||
conv(graph, feat_dict)
|
||||
|
||||
return t.elapsed_secs / 50
|
||||
@@ -0,0 +1,34 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("graph_name", ["livejournal", "reddit"])
|
||||
@utils.parametrize("format", ["coo", "csc"])
|
||||
@utils.parametrize("seed_nodes_num", [200, 5000, 20000])
|
||||
def track_time(graph_name, format, seed_nodes_num):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
|
||||
seed_nodes = np.random.randint(0, graph.num_nodes(), seed_nodes_num)
|
||||
seed_nodes = torch.from_numpy(seed_nodes).to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
dgl.node_subgraph(graph, seed_nodes)
|
||||
|
||||
# timing
|
||||
num_iters = 50
|
||||
with utils.Timer() as t:
|
||||
for i in range(num_iters):
|
||||
dgl.node_subgraph(graph, seed_nodes)
|
||||
|
||||
return t.elapsed_secs / num_iters
|
||||
@@ -0,0 +1,39 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
def _random_walk(g, seeds, length):
|
||||
return dgl.sampling.random_walk(g, seeds, length=length)
|
||||
|
||||
|
||||
def _node2vec(g, seeds, length):
|
||||
return dgl.sampling.node2vec_random_walk(g, seeds, 1, 1, length)
|
||||
|
||||
|
||||
@utils.skip_if_gpu()
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("graph_name", ["cora", "livejournal", "friendster"])
|
||||
@utils.parametrize("num_seeds", [10, 100, 1000])
|
||||
@utils.parametrize("length", [2, 5, 10, 20])
|
||||
@utils.parametrize("algorithm", ["_random_walk", "_node2vec"])
|
||||
def track_time(graph_name, num_seeds, length, algorithm):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, "csr")
|
||||
seeds = torch.randint(0, graph.num_nodes(), (num_seeds,))
|
||||
print(graph_name, num_seeds, length)
|
||||
alg = globals()[algorithm]
|
||||
# dry run
|
||||
for i in range(5):
|
||||
_ = alg(graph, seeds, length=length)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(50):
|
||||
_ = alg(graph, seeds, length=length)
|
||||
|
||||
return t.elapsed_secs / 50
|
||||
@@ -0,0 +1,39 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("batch_size", [4, 256, 1024])
|
||||
@utils.parametrize("feat_size", [16, 128, 512])
|
||||
@utils.parametrize("readout_op", ["sum", "max", "min", "mean"])
|
||||
@utils.parametrize("type", ["edge", "node"])
|
||||
def track_time(batch_size, feat_size, readout_op, type):
|
||||
device = utils.get_bench_device()
|
||||
ds = dgl.data.QM7bDataset()
|
||||
# prepare graph
|
||||
graphs = ds[0:batch_size][0]
|
||||
|
||||
g = dgl.batch(graphs).to(device)
|
||||
if type == "node":
|
||||
g.ndata["h"] = torch.randn((g.num_nodes(), feat_size), device=device)
|
||||
for i in range(10):
|
||||
out = dgl.readout_nodes(g, "h", op=readout_op)
|
||||
with utils.Timer() as t:
|
||||
for i in range(50):
|
||||
out = dgl.readout_nodes(g, "h", op=readout_op)
|
||||
elif type == "edge":
|
||||
g.edata["h"] = torch.randn((g.num_edges(), feat_size), device=device)
|
||||
for i in range(10):
|
||||
out = dgl.readout_edges(g, "h", op=readout_op)
|
||||
with utils.Timer() as t:
|
||||
for i in range(50):
|
||||
out = dgl.readout_edges(g, "h", op=readout_op)
|
||||
else:
|
||||
raise Exception("Unknown type")
|
||||
|
||||
return t.elapsed_secs / 50
|
||||
@@ -0,0 +1,28 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=1200)
|
||||
@utils.parametrize_cpu("graph_name", ["cora", "livejournal", "friendster"])
|
||||
@utils.parametrize_gpu("graph_name", ["cora", "livejournal"])
|
||||
@utils.parametrize("format", ["coo", "csc", "csr"])
|
||||
def track_time(graph_name, format):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
graph = graph.formats([format])
|
||||
# dry run
|
||||
dgl.reverse(graph)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(100):
|
||||
gg = dgl.reverse(graph)
|
||||
|
||||
return t.elapsed_secs / 100
|
||||
@@ -0,0 +1,39 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize_cpu("graph_name", ["livejournal", "reddit"])
|
||||
@utils.parametrize_gpu("graph_name", ["ogbn-arxiv", "reddit"])
|
||||
@utils.parametrize("format", ["coo", "csc"])
|
||||
@utils.parametrize("seed_nodes_num", [200, 5000, 20000])
|
||||
@utils.parametrize("fanout", [5, 20, 40])
|
||||
def track_time(graph_name, format, seed_nodes_num, fanout):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format).to(device)
|
||||
|
||||
edge_dir = "in"
|
||||
seed_nodes = np.random.randint(0, graph.num_nodes(), seed_nodes_num)
|
||||
seed_nodes = torch.from_numpy(seed_nodes).to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
dgl.sampling.sample_neighbors(
|
||||
graph, seed_nodes, fanout, edge_dir=edge_dir
|
||||
)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(50):
|
||||
dgl.sampling.sample_neighbors(
|
||||
graph, seed_nodes, fanout, edge_dir=edge_dir
|
||||
)
|
||||
|
||||
return t.elapsed_secs / 50
|
||||
@@ -0,0 +1,37 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.skip_if_gpu()
|
||||
@utils.benchmark("time", timeout=1200)
|
||||
@utils.parametrize("graph_name", ["reddit", "ogbn-products"])
|
||||
@utils.parametrize("num_seed_nodes", [32, 256, 1024, 2048])
|
||||
@utils.parametrize("fanout", [5, 10, 20])
|
||||
def track_time(graph_name, num_seed_nodes, fanout):
|
||||
device = utils.get_bench_device()
|
||||
data = utils.process_data(graph_name)
|
||||
graph = data[0]
|
||||
|
||||
# dry run
|
||||
dgl.sampling.sample_neighbors(graph, [1, 2, 3], fanout)
|
||||
|
||||
subg_list = []
|
||||
for i in range(10):
|
||||
seed_nodes = np.random.randint(
|
||||
0, graph.num_nodes(), size=num_seed_nodes
|
||||
)
|
||||
subg = dgl.sampling.sample_neighbors(graph, seed_nodes, fanout)
|
||||
subg_list.append(subg)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(10):
|
||||
gg = dgl.to_block(subg_list[i])
|
||||
|
||||
return t.elapsed_secs / 10
|
||||
@@ -0,0 +1,38 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=7200)
|
||||
@utils.parametrize("graph_name", ["ogbn-arxiv", "pubmed"])
|
||||
@utils.parametrize("format", ["coo"]) # only coo supports udf
|
||||
@utils.parametrize("feat_size", [8, 32, 128, 512])
|
||||
@utils.parametrize("reduce_type", ["u->e", "u+v"])
|
||||
def track_time(graph_name, format, feat_size, reduce_type):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
graph.ndata["h"] = torch.randn(
|
||||
(graph.num_nodes(), feat_size), device=device
|
||||
)
|
||||
|
||||
reduce_udf_dict = {
|
||||
"u->e": lambda edges: {"x": edges.src["h"]},
|
||||
"u+v": lambda edges: {"x": edges.src["h"] + edges.dst["h"]},
|
||||
}
|
||||
|
||||
# dry run
|
||||
graph.apply_edges(reduce_udf_dict[reduce_type])
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
graph.apply_edges(reduce_udf_dict[reduce_type])
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,52 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("feat_size", [32, 128, 512])
|
||||
@utils.parametrize("num_relations", [5, 50, 500])
|
||||
@utils.parametrize("multi_reduce_type", ["sum", "stack"])
|
||||
def track_time(feat_size, num_relations, multi_reduce_type):
|
||||
device = utils.get_bench_device()
|
||||
dd = {}
|
||||
candidate_edges = [
|
||||
dgl.data.CoraGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.PubmedGraphDataset(verbose=False)[0].edges(),
|
||||
dgl.data.CiteseerGraphDataset(verbose=False)[0].edges(),
|
||||
]
|
||||
for i in range(num_relations):
|
||||
dd[("n1", "e_{}".format(i), "n2")] = candidate_edges[
|
||||
i % len(candidate_edges)
|
||||
]
|
||||
graph = dgl.heterograph(dd)
|
||||
|
||||
graph = graph.to(device)
|
||||
graph.nodes["n1"].data["h"] = torch.randn(
|
||||
(graph.num_nodes("n1"), feat_size), device=device
|
||||
)
|
||||
graph.nodes["n2"].data["h"] = torch.randn(
|
||||
(graph.num_nodes("n2"), feat_size), device=device
|
||||
)
|
||||
|
||||
# dry run
|
||||
update_dict = {}
|
||||
for i in range(num_relations):
|
||||
update_dict["e_{}".format(i)] = (
|
||||
lambda edges: {"x": edges.src["h"]},
|
||||
lambda nodes: {"h_new": torch.sum(nodes.mailbox["x"], dim=1)},
|
||||
)
|
||||
graph.multi_update_all(update_dict, multi_reduce_type)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
graph.multi_update_all(update_dict, multi_reduce_type)
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,48 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("graph_name", ["pubmed", "ogbn-arxiv"])
|
||||
@utils.parametrize("format", ["coo"]) # only coo supports udf
|
||||
@utils.parametrize("feat_size", [8, 64, 512])
|
||||
@utils.parametrize("msg_type", ["copy_u", "u_mul_e"])
|
||||
@utils.parametrize("reduce_type", ["sum", "mean", "max"])
|
||||
def track_time(graph_name, format, feat_size, msg_type, reduce_type):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph_name, format)
|
||||
graph = graph.to(device)
|
||||
graph.ndata["h"] = torch.randn(
|
||||
(graph.num_nodes(), feat_size), device=device
|
||||
)
|
||||
graph.edata["e"] = torch.randn((graph.num_edges(), 1), device=device)
|
||||
|
||||
msg_udf_dict = {
|
||||
"copy_u": lambda edges: {"x": edges.src["h"]},
|
||||
"u_mul_e": lambda edges: {"x": edges.src["h"] * edges.data["e"]},
|
||||
}
|
||||
|
||||
reduct_udf_dict = {
|
||||
"sum": lambda nodes: {"h_new": torch.sum(nodes.mailbox["x"], dim=1)},
|
||||
"mean": lambda nodes: {"h_new": torch.mean(nodes.mailbox["x"], dim=1)},
|
||||
"max": lambda nodes: {"h_new": torch.max(nodes.mailbox["x"], dim=1)[0]},
|
||||
}
|
||||
|
||||
# dry run
|
||||
graph.update_all(msg_udf_dict[msg_type], reduct_udf_dict[reduce_type])
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(3):
|
||||
graph.update_all(
|
||||
msg_udf_dict[msg_type], reduct_udf_dict[reduce_type]
|
||||
)
|
||||
|
||||
return t.elapsed_secs / 3
|
||||
@@ -0,0 +1,28 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("batch_size", [4, 32, 256, 1024])
|
||||
def track_time(batch_size):
|
||||
device = utils.get_bench_device()
|
||||
ds = dgl.data.QM7bDataset()
|
||||
# prepare graph
|
||||
graphs = ds[0:batch_size][0]
|
||||
bg = dgl.batch(graphs).to(device)
|
||||
|
||||
# dry run
|
||||
for i in range(10):
|
||||
glist = dgl.unbatch(bg)
|
||||
|
||||
# timing
|
||||
with utils.Timer() as t:
|
||||
for i in range(100):
|
||||
glist = dgl.unbatch(bg)
|
||||
|
||||
return t.elapsed_secs / 100
|
||||
@@ -0,0 +1,33 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
# The benchmarks for ops edge_softmax
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("graph", ["ogbn-arxiv", "reddit", "cora", "pubmed"])
|
||||
@utils.parametrize("num_heads", [1, 4, 8])
|
||||
def track_time(graph, num_heads):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph).to(device)
|
||||
score = (
|
||||
torch.randn((graph.num_edges(), num_heads))
|
||||
.requires_grad_(True)
|
||||
.float()
|
||||
.to(device)
|
||||
)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
y = dgl.ops.edge_softmax(graph, score)
|
||||
|
||||
# timing
|
||||
with utils.Timer(device) as t:
|
||||
for i in range(100):
|
||||
y = dgl.ops.edge_softmax(graph, score)
|
||||
|
||||
return t.elapsed_secs / 100
|
||||
@@ -0,0 +1,46 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
def calc_gflops(graph, feat_size, num_heads, time):
|
||||
return round(
|
||||
2 * graph.num_edges() * feat_size / 1000000000 / time, 2
|
||||
) # count both mul and add
|
||||
|
||||
|
||||
# The benchmarks include broadcasting cases.
|
||||
# Given feat_size = D, num_heads = H, the node feature shape will be (H, D // H)
|
||||
# while the edge feature shape will be (H, ), so tested operations will broadcast
|
||||
# along the last dimension. The total FLOP is controlled by the feat_size no
|
||||
# matter how many heads are there.
|
||||
# If num_heads = 0, it falls back to the normal element-wise operation without
|
||||
# broadcasting.
|
||||
@utils.benchmark("flops", timeout=600)
|
||||
@utils.parametrize("graph", ["ogbn-arxiv", "reddit", "ogbn-proteins"])
|
||||
@utils.parametrize("feat_size", [4, 32, 256])
|
||||
@utils.parametrize("num_heads", [0, 1, 4])
|
||||
def track_flops(graph, feat_size, num_heads):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph, format="coo").to(device)
|
||||
if num_heads == 0:
|
||||
x = torch.randn(graph.num_nodes(), feat_size, device=device)
|
||||
else:
|
||||
x = torch.randn(
|
||||
graph.num_nodes(), num_heads, feat_size // num_heads, device=device
|
||||
)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
y = dgl.ops.u_dot_v(graph, x, x)
|
||||
|
||||
# timing
|
||||
with utils.Timer(device) as t:
|
||||
for i in range(10):
|
||||
y = dgl.ops.u_dot_v(graph, x, x)
|
||||
|
||||
return calc_gflops(graph, feat_size, num_heads, t.elapsed_secs / 10)
|
||||
@@ -0,0 +1,39 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
def calc_gflops(graph, feat_size, time):
|
||||
return round(graph.num_edges() * feat_size / 1000000000 / time, 2)
|
||||
|
||||
|
||||
@utils.benchmark("flops", timeout=600)
|
||||
@utils.parametrize("graph", ["ogbn-arxiv", "reddit", "ogbn-proteins"])
|
||||
@utils.parametrize("feat_size", [4, 32, 256])
|
||||
@utils.parametrize("reducer", ["sum", "max"])
|
||||
def track_flops(graph, feat_size, reducer):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph, format="csc").to(device)
|
||||
x = torch.randn(graph.num_nodes(), feat_size, device=device)
|
||||
|
||||
if reducer == "sum":
|
||||
op = dgl.ops.copy_u_sum
|
||||
elif reducer == "max":
|
||||
op = dgl.ops.copy_u_max
|
||||
else:
|
||||
raise ValueError("Invalid reducer", reducer)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
y = op(graph, x)
|
||||
|
||||
# timing
|
||||
with utils.Timer(device) as t:
|
||||
for i in range(10):
|
||||
y = op(graph, x)
|
||||
|
||||
return calc_gflops(graph, feat_size, t.elapsed_secs / 10)
|
||||
@@ -0,0 +1,48 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
def calc_gflops(graph, feat_size, num_heads, time):
|
||||
return round(
|
||||
2 * graph.num_edges() * feat_size / 1000000000 / time, 2
|
||||
) # count both mul and add
|
||||
|
||||
|
||||
# The benchmarks include broadcasting cases.
|
||||
# Given feat_size = D, num_heads = H, the node feature shape will be (H, D // H)
|
||||
# while the edge feature shape will be (H, ), so tested operations will broadcast
|
||||
# along the last dimension. The total FLOP is controlled by the feat_size no
|
||||
# matter how many heads are there.
|
||||
# If num_heads = 0, it falls back to the normal element-wise operation without
|
||||
# broadcasting.
|
||||
@utils.benchmark("flops", timeout=600)
|
||||
@utils.parametrize("graph", ["ogbn-arxiv", "reddit", "ogbn-proteins"])
|
||||
@utils.parametrize("feat_size", [4, 32, 256])
|
||||
@utils.parametrize("num_heads", [0, 1, 4])
|
||||
def track_flops(graph, feat_size, num_heads):
|
||||
device = utils.get_bench_device()
|
||||
graph = utils.get_graph(graph, format="csc").to(device)
|
||||
if num_heads == 0:
|
||||
x = torch.randn(graph.num_nodes(), feat_size, device=device)
|
||||
w = torch.randn(graph.num_edges(), feat_size, device=device)
|
||||
else:
|
||||
x = torch.randn(
|
||||
graph.num_nodes(), num_heads, feat_size // num_heads, device=device
|
||||
)
|
||||
w = torch.randn(graph.num_edges(), num_heads, 1, device=device)
|
||||
|
||||
# dry run
|
||||
for i in range(3):
|
||||
y = dgl.ops.u_mul_e_sum(graph, x, w)
|
||||
|
||||
# timing
|
||||
with utils.Timer(device) as t:
|
||||
for i in range(10):
|
||||
y = dgl.ops.u_mul_e_sum(graph, x, w)
|
||||
|
||||
return calc_gflops(graph, feat_size, num_heads, t.elapsed_secs / 10)
|
||||
@@ -0,0 +1,127 @@
|
||||
import dgl
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn.pytorch import GATConv
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class GAT(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
num_layers,
|
||||
in_dim,
|
||||
num_hidden,
|
||||
num_classes,
|
||||
heads,
|
||||
activation,
|
||||
feat_drop,
|
||||
attn_drop,
|
||||
negative_slope,
|
||||
residual,
|
||||
):
|
||||
super(GAT, self).__init__()
|
||||
self.num_layers = num_layers
|
||||
self.gat_layers = nn.ModuleList()
|
||||
self.activation = activation
|
||||
# input projection (no residual)
|
||||
self.gat_layers.append(
|
||||
GATConv(
|
||||
in_dim,
|
||||
num_hidden,
|
||||
heads[0],
|
||||
feat_drop,
|
||||
attn_drop,
|
||||
negative_slope,
|
||||
False,
|
||||
self.activation,
|
||||
)
|
||||
)
|
||||
# hidden layers
|
||||
for l in range(1, num_layers):
|
||||
# due to multi-head, the in_dim = num_hidden * num_heads
|
||||
self.gat_layers.append(
|
||||
GATConv(
|
||||
num_hidden * heads[l - 1],
|
||||
num_hidden,
|
||||
heads[l],
|
||||
feat_drop,
|
||||
attn_drop,
|
||||
negative_slope,
|
||||
residual,
|
||||
self.activation,
|
||||
)
|
||||
)
|
||||
# output projection
|
||||
self.gat_layers.append(
|
||||
GATConv(
|
||||
num_hidden * heads[-2],
|
||||
num_classes,
|
||||
heads[-1],
|
||||
feat_drop,
|
||||
attn_drop,
|
||||
negative_slope,
|
||||
residual,
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, g, inputs):
|
||||
h = inputs
|
||||
for l in range(self.num_layers):
|
||||
h = self.gat_layers[l](g, h).flatten(1)
|
||||
# output projection
|
||||
logits = self.gat_layers[-1](g, h).mean(1)
|
||||
return logits
|
||||
|
||||
|
||||
def evaluate(model, g, features, labels, mask):
|
||||
model.eval()
|
||||
with torch.no_grad():
|
||||
logits = model(g, features)
|
||||
logits = logits[mask]
|
||||
labels = labels[mask]
|
||||
_, indices = torch.max(logits, dim=1)
|
||||
correct = torch.sum(indices == labels)
|
||||
return correct.item() * 1.0 / len(labels) * 100
|
||||
|
||||
|
||||
@utils.benchmark("acc")
|
||||
@utils.parametrize("data", ["cora", "pubmed"])
|
||||
def track_acc(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
g = data[0].to(device)
|
||||
|
||||
features = g.ndata["feat"]
|
||||
labels = g.ndata["label"]
|
||||
train_mask = g.ndata["train_mask"]
|
||||
val_mask = g.ndata["val_mask"]
|
||||
test_mask = g.ndata["test_mask"]
|
||||
|
||||
in_feats = features.shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
g = dgl.remove_self_loop(g)
|
||||
g = dgl.add_self_loop(g)
|
||||
|
||||
# create model
|
||||
model = GAT(1, in_feats, 8, n_classes, [8, 1], F.elu, 0.6, 0.6, 0.2, False)
|
||||
loss_fcn = torch.nn.CrossEntropyLoss()
|
||||
|
||||
model = model.to(device)
|
||||
model.train()
|
||||
|
||||
# optimizer
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
|
||||
for epoch in range(200):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
acc = evaluate(model, g, features, labels, test_mask)
|
||||
return acc
|
||||
@@ -0,0 +1,90 @@
|
||||
import dgl
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn.pytorch import GraphConv
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class GCN(nn.Module):
|
||||
def __init__(
|
||||
self, in_feats, n_hidden, n_classes, n_layers, activation, dropout
|
||||
):
|
||||
super(GCN, self).__init__()
|
||||
self.layers = nn.ModuleList()
|
||||
# input layer
|
||||
self.layers.append(GraphConv(in_feats, n_hidden, activation=activation))
|
||||
# hidden layers
|
||||
for i in range(n_layers - 1):
|
||||
self.layers.append(
|
||||
GraphConv(n_hidden, n_hidden, activation=activation)
|
||||
)
|
||||
# output layer
|
||||
self.layers.append(GraphConv(n_hidden, n_classes))
|
||||
self.dropout = nn.Dropout(p=dropout)
|
||||
|
||||
def forward(self, g, features):
|
||||
h = features
|
||||
for i, layer in enumerate(self.layers):
|
||||
if i != 0:
|
||||
h = self.dropout(h)
|
||||
h = layer(g, h)
|
||||
return h
|
||||
|
||||
|
||||
def evaluate(model, g, features, labels, mask):
|
||||
model.eval()
|
||||
with torch.no_grad():
|
||||
logits = model(g, features)
|
||||
logits = logits[mask]
|
||||
labels = labels[mask]
|
||||
_, indices = torch.max(logits, dim=1)
|
||||
correct = torch.sum(indices == labels)
|
||||
return correct.item() * 1.0 / len(labels) * 100
|
||||
|
||||
|
||||
@utils.benchmark("acc")
|
||||
@utils.parametrize("data", ["cora", "pubmed"])
|
||||
def track_acc(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
g = data[0].to(device).int()
|
||||
|
||||
features = g.ndata["feat"]
|
||||
labels = g.ndata["label"]
|
||||
train_mask = g.ndata["train_mask"]
|
||||
val_mask = g.ndata["val_mask"]
|
||||
test_mask = g.ndata["test_mask"]
|
||||
|
||||
in_feats = features.shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
g = dgl.remove_self_loop(g)
|
||||
g = dgl.add_self_loop(g)
|
||||
|
||||
# normalization
|
||||
degs = g.in_degrees().float()
|
||||
norm = torch.pow(degs, -0.5)
|
||||
norm[torch.isinf(norm)] = 0
|
||||
g.ndata["norm"] = norm.unsqueeze(1)
|
||||
|
||||
# create GCN model
|
||||
model = GCN(in_feats, 16, n_classes, 1, F.relu, 0.5)
|
||||
loss_fcn = torch.nn.CrossEntropyLoss()
|
||||
|
||||
model = model.to(device)
|
||||
model.train()
|
||||
|
||||
# optimizer
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
|
||||
for epoch in range(200):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
acc = evaluate(model, g, features, labels, test_mask)
|
||||
return acc
|
||||
@@ -0,0 +1,125 @@
|
||||
import dgl
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class GraphConv(nn.Module):
|
||||
def __init__(self, in_dim, out_dim, activation=None):
|
||||
super(GraphConv, self).__init__()
|
||||
self.in_dim = in_dim
|
||||
self.out_dim = out_dim
|
||||
self.activation = activation
|
||||
self.weight = nn.Parameter(torch.Tensor(in_dim, out_dim))
|
||||
self.bias = nn.Parameter(torch.Tensor(out_dim))
|
||||
nn.init.xavier_normal_(self.weight)
|
||||
nn.init.zeros_(self.bias)
|
||||
|
||||
def forward(self, graph, feat):
|
||||
with graph.local_scope():
|
||||
graph.ndata["ci"] = torch.pow(
|
||||
graph.out_degrees().float().clamp(min=1), -0.5
|
||||
)
|
||||
graph.ndata["cj"] = torch.pow(
|
||||
graph.in_degrees().float().clamp(min=1), -0.5
|
||||
)
|
||||
graph.ndata["h"] = feat
|
||||
graph.update_all(self.mfunc, self.rfunc)
|
||||
h = graph.ndata["h"]
|
||||
h = torch.matmul(h, self.weight) + self.bias
|
||||
if self.activation is not None:
|
||||
h = self.activation(h)
|
||||
return h
|
||||
|
||||
def mfunc(self, edges):
|
||||
return {"m": edges.src["h"], "ci": edges.src["ci"]}
|
||||
|
||||
def rfunc(self, nodes):
|
||||
ci = nodes.mailbox["ci"].unsqueeze(2)
|
||||
newh = (nodes.mailbox["m"] * ci).sum(1) * nodes.data["cj"].unsqueeze(1)
|
||||
return {"h": newh}
|
||||
|
||||
|
||||
class GCN(nn.Module):
|
||||
def __init__(
|
||||
self, in_feats, n_hidden, n_classes, n_layers, activation, dropout
|
||||
):
|
||||
super(GCN, self).__init__()
|
||||
self.layers = nn.ModuleList()
|
||||
# input layer
|
||||
self.layers.append(GraphConv(in_feats, n_hidden, activation=activation))
|
||||
# hidden layers
|
||||
for i in range(n_layers - 1):
|
||||
self.layers.append(
|
||||
GraphConv(n_hidden, n_hidden, activation=activation)
|
||||
)
|
||||
# output layer
|
||||
self.layers.append(GraphConv(n_hidden, n_classes))
|
||||
self.dropout = nn.Dropout(p=dropout)
|
||||
|
||||
def forward(self, g, features):
|
||||
h = features
|
||||
for i, layer in enumerate(self.layers):
|
||||
if i != 0:
|
||||
h = self.dropout(h)
|
||||
h = layer(g, h)
|
||||
return h
|
||||
|
||||
|
||||
def evaluate(model, g, features, labels, mask):
|
||||
model.eval()
|
||||
with torch.no_grad():
|
||||
logits = model(g, features)
|
||||
logits = logits[mask]
|
||||
labels = labels[mask]
|
||||
_, indices = torch.max(logits, dim=1)
|
||||
correct = torch.sum(indices == labels)
|
||||
return correct.item() * 1.0 / len(labels) * 100
|
||||
|
||||
|
||||
@utils.benchmark("acc", timeout=300)
|
||||
@utils.parametrize("data", ["cora", "pubmed"])
|
||||
def track_acc(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
g = data[0].to(device).int()
|
||||
|
||||
features = g.ndata["feat"]
|
||||
labels = g.ndata["label"]
|
||||
train_mask = g.ndata["train_mask"]
|
||||
val_mask = g.ndata["val_mask"]
|
||||
test_mask = g.ndata["test_mask"]
|
||||
|
||||
in_feats = features.shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
g = dgl.remove_self_loop(g)
|
||||
g = dgl.add_self_loop(g)
|
||||
|
||||
# normalization
|
||||
degs = g.in_degrees().float()
|
||||
norm = torch.pow(degs, -0.5)
|
||||
norm[torch.isinf(norm)] = 0
|
||||
g.ndata["norm"] = norm.unsqueeze(1)
|
||||
|
||||
# create GCN model
|
||||
model = GCN(in_feats, 16, n_classes, 1, F.relu, 0.5)
|
||||
loss_fcn = torch.nn.CrossEntropyLoss()
|
||||
|
||||
model = model.to(device)
|
||||
model.train()
|
||||
|
||||
# optimizer
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
|
||||
for epoch in range(200):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
acc = evaluate(model, g, features, labels, test_mask)
|
||||
return acc
|
||||
@@ -0,0 +1,71 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torchmetrics.functional import accuracy
|
||||
|
||||
from .. import rgcn, utils
|
||||
|
||||
|
||||
@utils.benchmark("acc", timeout=1200)
|
||||
@utils.parametrize("dataset", ["aifb", "mutag"])
|
||||
@utils.parametrize("ns_mode", [False])
|
||||
def track_acc(dataset, ns_mode):
|
||||
(
|
||||
g,
|
||||
num_rels,
|
||||
num_classes,
|
||||
labels,
|
||||
train_idx,
|
||||
test_idx,
|
||||
target_idx,
|
||||
) = rgcn.load_data(dataset, get_norm=True)
|
||||
num_hidden = 16
|
||||
if dataset == "aifb":
|
||||
num_bases = -1
|
||||
l2norm = 0.0
|
||||
elif dataset == "mutag":
|
||||
num_bases = 30
|
||||
l2norm = 5e-4
|
||||
elif dataset == "am":
|
||||
num_bases = 40
|
||||
l2norm = 5e-4
|
||||
else:
|
||||
raise ValueError()
|
||||
model = rgcn.RGCN(
|
||||
g.num_nodes(),
|
||||
num_hidden,
|
||||
num_classes,
|
||||
num_rels,
|
||||
num_bases=num_bases,
|
||||
ns_mode=ns_mode,
|
||||
)
|
||||
device = utils.get_bench_device()
|
||||
labels = labels.to(device)
|
||||
model = model.to(device)
|
||||
g = g.int().to(device)
|
||||
|
||||
optimizer = torch.optim.Adam(
|
||||
model.parameters(), lr=1e-2, weight_decay=l2norm
|
||||
)
|
||||
|
||||
model.train()
|
||||
for epoch in range(30):
|
||||
logits = model(g)
|
||||
logits = logits[target_idx]
|
||||
loss = F.cross_entropy(logits[train_idx], labels[train_idx])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
model.eval()
|
||||
with torch.no_grad():
|
||||
logits = model(g)
|
||||
logits = logits[target_idx]
|
||||
test_acc = accuracy(
|
||||
logits[test_idx].argmax(dim=1),
|
||||
labels[test_idx],
|
||||
task="multiclass",
|
||||
num_classes=num_classes,
|
||||
).item()
|
||||
|
||||
return test_acc
|
||||
@@ -0,0 +1,400 @@
|
||||
import itertools
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.nn.pytorch as dglnn
|
||||
import torch as th
|
||||
import torch.multiprocessing as mp
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
from dgl.nn import RelGraphConv
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class EntityClassify(nn.Module):
|
||||
"""Entity classification class for RGCN
|
||||
Parameters
|
||||
----------
|
||||
device : int
|
||||
Device to run the layer.
|
||||
num_nodes : int
|
||||
Number of nodes.
|
||||
h_dim : int
|
||||
Hidden dim size.
|
||||
out_dim : int
|
||||
Output dim size.
|
||||
num_rels : int
|
||||
Numer of relation types.
|
||||
num_bases : int
|
||||
Number of bases. If is none, use number of relations.
|
||||
num_hidden_layers : int
|
||||
Number of hidden RelGraphConv Layer
|
||||
dropout : float
|
||||
Dropout
|
||||
use_self_loop : bool
|
||||
Use self loop if True, default False.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
num_nodes,
|
||||
h_dim,
|
||||
out_dim,
|
||||
num_rels,
|
||||
num_bases=None,
|
||||
num_hidden_layers=1,
|
||||
dropout=0,
|
||||
use_self_loop=False,
|
||||
layer_norm=False,
|
||||
):
|
||||
super(EntityClassify, self).__init__()
|
||||
self.device = device
|
||||
self.num_nodes = num_nodes
|
||||
self.h_dim = h_dim
|
||||
self.out_dim = out_dim
|
||||
self.num_rels = num_rels
|
||||
self.num_bases = None if num_bases < 0 else num_bases
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.dropout = dropout
|
||||
self.use_self_loop = use_self_loop
|
||||
self.layer_norm = layer_norm
|
||||
|
||||
self.layers = nn.ModuleList()
|
||||
# i2h
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.h_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=F.relu,
|
||||
self_loop=self.use_self_loop,
|
||||
dropout=self.dropout,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
# h2h
|
||||
for idx in range(self.num_hidden_layers):
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.h_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=F.relu,
|
||||
self_loop=self.use_self_loop,
|
||||
dropout=self.dropout,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
# h2o
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.out_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=None,
|
||||
self_loop=self.use_self_loop,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, blocks, feats, norm=None):
|
||||
if blocks is None:
|
||||
# full graph training
|
||||
blocks = [self.g] * len(self.layers)
|
||||
h = feats
|
||||
for layer, block in zip(self.layers, blocks):
|
||||
block = block.to(self.device)
|
||||
h = layer(block, h, block.edata["etype"], block.edata["norm"])
|
||||
return h
|
||||
|
||||
|
||||
class RelGraphEmbedLayer(nn.Module):
|
||||
r"""Embedding layer for featureless heterograph.
|
||||
Parameters
|
||||
----------
|
||||
device : int
|
||||
Device to run the layer.
|
||||
num_nodes : int
|
||||
Number of nodes.
|
||||
node_tides : tensor
|
||||
Storing the node type id for each node starting from 0
|
||||
num_of_ntype : int
|
||||
Number of node types
|
||||
input_size : list of int
|
||||
A list of input feature size for each node type. If None, we then
|
||||
treat certain input feature as an one-hot encoding feature.
|
||||
embed_size : int
|
||||
Output embed size
|
||||
embed_name : str, optional
|
||||
Embed name
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
num_nodes,
|
||||
node_tids,
|
||||
num_of_ntype,
|
||||
input_size,
|
||||
embed_size,
|
||||
sparse_emb=False,
|
||||
embed_name="embed",
|
||||
):
|
||||
super(RelGraphEmbedLayer, self).__init__()
|
||||
self.device = device
|
||||
self.embed_size = embed_size
|
||||
self.embed_name = embed_name
|
||||
self.num_nodes = num_nodes
|
||||
self.sparse_emb = sparse_emb
|
||||
|
||||
# create weight embeddings for each node for each relation
|
||||
self.embeds = nn.ParameterDict()
|
||||
self.num_of_ntype = num_of_ntype
|
||||
self.idmap = th.empty(num_nodes).long()
|
||||
|
||||
for ntype in range(num_of_ntype):
|
||||
if input_size[ntype] is not None:
|
||||
input_emb_size = input_size[ntype].shape[1]
|
||||
embed = nn.Parameter(th.Tensor(input_emb_size, self.embed_size))
|
||||
nn.init.xavier_uniform_(embed)
|
||||
self.embeds[str(ntype)] = embed
|
||||
|
||||
self.node_embeds = th.nn.Embedding(
|
||||
node_tids.shape[0], self.embed_size, sparse=self.sparse_emb
|
||||
)
|
||||
nn.init.uniform_(self.node_embeds.weight, -1.0, 1.0)
|
||||
|
||||
def forward(self, node_ids, node_tids, type_ids, features):
|
||||
"""Forward computation
|
||||
Parameters
|
||||
----------
|
||||
node_ids : tensor
|
||||
node ids to generate embedding for.
|
||||
node_tids : tensor
|
||||
node type ids
|
||||
features : list of features
|
||||
list of initial features for nodes belong to different node type.
|
||||
If None, the corresponding features is an one-hot encoding feature,
|
||||
else use the features directly as input feature and matmul a
|
||||
projection matrix.
|
||||
Returns
|
||||
-------
|
||||
tensor
|
||||
embeddings as the input of the next layer
|
||||
"""
|
||||
tsd_ids = node_ids.to(self.node_embeds.weight.device)
|
||||
embeds = th.empty(
|
||||
node_ids.shape[0], self.embed_size, device=self.device
|
||||
)
|
||||
for ntype in range(self.num_of_ntype):
|
||||
if features[ntype] is not None:
|
||||
loc = node_tids == ntype
|
||||
embeds[loc] = features[ntype][type_ids[loc]].to(
|
||||
self.device
|
||||
) @ self.embeds[str(ntype)].to(self.device)
|
||||
else:
|
||||
loc = node_tids == ntype
|
||||
embeds[loc] = self.node_embeds(tsd_ids[loc]).to(self.device)
|
||||
|
||||
return embeds
|
||||
|
||||
|
||||
def evaluate(model, embed_layer, eval_loader, node_feats):
|
||||
model.eval()
|
||||
embed_layer.eval()
|
||||
eval_logits = []
|
||||
eval_seeds = []
|
||||
|
||||
with th.no_grad():
|
||||
for sample_data in eval_loader:
|
||||
th.cuda.empty_cache()
|
||||
_, _, blocks = sample_data
|
||||
feats = embed_layer(
|
||||
blocks[0].srcdata[dgl.NID],
|
||||
blocks[0].srcdata[dgl.NTYPE],
|
||||
blocks[0].srcdata["type_id"],
|
||||
node_feats,
|
||||
)
|
||||
logits = model(blocks, feats)
|
||||
eval_logits.append(logits.cpu().detach())
|
||||
eval_seeds.append(blocks[-1].dstdata["type_id"].cpu().detach())
|
||||
eval_logits = th.cat(eval_logits)
|
||||
eval_seeds = th.cat(eval_seeds)
|
||||
|
||||
return eval_logits, eval_seeds
|
||||
|
||||
|
||||
@utils.benchmark("acc", timeout=3600) # ogbn-mag takes ~1 hour to train
|
||||
@utils.parametrize("data", ["am", "ogbn-mag"])
|
||||
def track_acc(data):
|
||||
dataset = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
if data == "am":
|
||||
n_bases = 40
|
||||
l2norm = 5e-4
|
||||
n_epochs = 20
|
||||
elif data == "ogbn-mag":
|
||||
n_bases = 2
|
||||
l2norm = 0
|
||||
n_epochs = 20
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
fanouts = [25, 15]
|
||||
n_layers = 2
|
||||
batch_size = 1024
|
||||
n_hidden = 64
|
||||
dropout = 0.5
|
||||
use_self_loop = True
|
||||
lr = 0.01
|
||||
num_workers = 4
|
||||
|
||||
hg = dataset[0]
|
||||
category = dataset.predict_category
|
||||
num_classes = dataset.num_classes
|
||||
train_mask = hg.nodes[category].data.pop("train_mask")
|
||||
train_idx = th.nonzero(train_mask, as_tuple=False).squeeze()
|
||||
test_mask = hg.nodes[category].data.pop("test_mask")
|
||||
test_idx = th.nonzero(test_mask, as_tuple=False).squeeze()
|
||||
labels = hg.nodes[category].data.pop("labels").to(device)
|
||||
num_of_ntype = len(hg.ntypes)
|
||||
num_rels = len(hg.canonical_etypes)
|
||||
|
||||
node_feats = []
|
||||
for ntype in hg.ntypes:
|
||||
if len(hg.nodes[ntype].data) == 0 or "feat" not in hg.nodes[ntype].data:
|
||||
node_feats.append(None)
|
||||
else:
|
||||
feat = hg.nodes[ntype].data.pop("feat")
|
||||
node_feats.append(feat.share_memory_())
|
||||
|
||||
# get target category id
|
||||
category_id = len(hg.ntypes)
|
||||
for i, ntype in enumerate(hg.ntypes):
|
||||
if ntype == category:
|
||||
category_id = i
|
||||
g = dgl.to_homogeneous(hg)
|
||||
u, v, eid = g.all_edges(form="all")
|
||||
|
||||
# global norm
|
||||
_, inverse_index, count = th.unique(
|
||||
v, return_inverse=True, return_counts=True
|
||||
)
|
||||
degrees = count[inverse_index]
|
||||
norm = th.ones(eid.shape[0]) / degrees
|
||||
norm = norm.unsqueeze(1)
|
||||
g.edata["norm"] = norm
|
||||
g.edata["etype"] = g.edata[dgl.ETYPE]
|
||||
g.ndata["type_id"] = g.ndata[dgl.NID]
|
||||
g.ndata["ntype"] = g.ndata[dgl.NTYPE]
|
||||
|
||||
node_ids = th.arange(g.num_nodes())
|
||||
# find out the target node ids
|
||||
node_tids = g.ndata[dgl.NTYPE]
|
||||
loc = node_tids == category_id
|
||||
target_nids = node_ids[loc]
|
||||
|
||||
g = g.formats("csc")
|
||||
sampler = dgl.dataloading.MultiLayerNeighborSampler(fanouts)
|
||||
train_loader = dgl.dataloading.DataLoader(
|
||||
g,
|
||||
target_nids[train_idx],
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
test_loader = dgl.dataloading.DataLoader(
|
||||
g,
|
||||
target_nids[test_idx],
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
# node features
|
||||
# None for one-hot feature, if not none, it should be the feature tensor.
|
||||
embed_layer = RelGraphEmbedLayer(
|
||||
device,
|
||||
g.num_nodes(),
|
||||
node_tids,
|
||||
num_of_ntype,
|
||||
node_feats,
|
||||
n_hidden,
|
||||
sparse_emb=True,
|
||||
)
|
||||
|
||||
# create model
|
||||
# all model params are in device.
|
||||
model = EntityClassify(
|
||||
device,
|
||||
g.num_nodes(),
|
||||
n_hidden,
|
||||
num_classes,
|
||||
num_rels,
|
||||
num_bases=n_bases,
|
||||
num_hidden_layers=n_layers - 2,
|
||||
dropout=dropout,
|
||||
use_self_loop=use_self_loop,
|
||||
layer_norm=False,
|
||||
)
|
||||
|
||||
embed_layer = embed_layer.to(device)
|
||||
model = model.to(device)
|
||||
|
||||
all_params = itertools.chain(
|
||||
model.parameters(), embed_layer.embeds.parameters()
|
||||
)
|
||||
optimizer = th.optim.Adam(all_params, lr=lr, weight_decay=l2norm)
|
||||
emb_optimizer = th.optim.SparseAdam(
|
||||
list(embed_layer.node_embeds.parameters()), lr=lr
|
||||
)
|
||||
|
||||
print("start training...")
|
||||
for epoch in range(n_epochs):
|
||||
model.train()
|
||||
embed_layer.train()
|
||||
|
||||
for i, sample_data in enumerate(train_loader):
|
||||
input_nodes, output_nodes, blocks = sample_data
|
||||
feats = embed_layer(
|
||||
input_nodes,
|
||||
blocks[0].srcdata["ntype"],
|
||||
blocks[0].srcdata["type_id"],
|
||||
node_feats,
|
||||
)
|
||||
logits = model(blocks, feats)
|
||||
seed_idx = blocks[-1].dstdata["type_id"]
|
||||
loss = F.cross_entropy(logits, labels[seed_idx])
|
||||
optimizer.zero_grad()
|
||||
emb_optimizer.zero_grad()
|
||||
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
emb_optimizer.step()
|
||||
|
||||
print("start testing...")
|
||||
|
||||
test_logits, test_seeds = evaluate(
|
||||
model, embed_layer, test_loader, node_feats
|
||||
)
|
||||
test_loss = F.cross_entropy(test_logits, labels[test_seeds].cpu()).item()
|
||||
test_acc = th.sum(
|
||||
test_logits.argmax(dim=1) == labels[test_seeds].cpu()
|
||||
).item() / len(test_seeds)
|
||||
|
||||
return test_acc
|
||||
@@ -0,0 +1,94 @@
|
||||
import dgl
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn.pytorch import SAGEConv
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class GraphSAGE(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_feats,
|
||||
n_hidden,
|
||||
n_classes,
|
||||
n_layers,
|
||||
activation,
|
||||
dropout,
|
||||
aggregator_type,
|
||||
):
|
||||
super(GraphSAGE, self).__init__()
|
||||
self.layers = nn.ModuleList()
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.activation = activation
|
||||
|
||||
# input layer
|
||||
self.layers.append(SAGEConv(in_feats, n_hidden, aggregator_type))
|
||||
# hidden layers
|
||||
for i in range(n_layers - 1):
|
||||
self.layers.append(SAGEConv(n_hidden, n_hidden, aggregator_type))
|
||||
# output layer
|
||||
self.layers.append(
|
||||
SAGEConv(n_hidden, n_classes, aggregator_type)
|
||||
) # activation None
|
||||
|
||||
def forward(self, graph, inputs):
|
||||
h = self.dropout(inputs)
|
||||
for l, layer in enumerate(self.layers):
|
||||
h = layer(graph, h)
|
||||
if l != len(self.layers) - 1:
|
||||
h = self.activation(h)
|
||||
h = self.dropout(h)
|
||||
return h
|
||||
|
||||
|
||||
def evaluate(model, g, features, labels, mask):
|
||||
model.eval()
|
||||
with torch.no_grad():
|
||||
logits = model(g, features)
|
||||
logits = logits[mask]
|
||||
labels = labels[mask]
|
||||
_, indices = torch.max(logits, dim=1)
|
||||
correct = torch.sum(indices == labels)
|
||||
return correct.item() * 1.0 / len(labels) * 100
|
||||
|
||||
|
||||
@utils.benchmark("acc")
|
||||
@utils.parametrize("data", ["cora", "pubmed"])
|
||||
def track_acc(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
g = data[0].to(device)
|
||||
|
||||
features = g.ndata["feat"]
|
||||
labels = g.ndata["label"]
|
||||
train_mask = g.ndata["train_mask"]
|
||||
val_mask = g.ndata["val_mask"]
|
||||
test_mask = g.ndata["test_mask"]
|
||||
|
||||
in_feats = features.shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
g = dgl.remove_self_loop(g)
|
||||
g = dgl.add_self_loop(g)
|
||||
|
||||
# create model
|
||||
model = GraphSAGE(in_feats, 16, n_classes, 1, F.relu, 0.5, "gcn")
|
||||
loss_fcn = torch.nn.CrossEntropyLoss()
|
||||
|
||||
model = model.to(device)
|
||||
model.train()
|
||||
|
||||
# optimizer
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
|
||||
for epoch in range(200):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
acc = evaluate(model, g, features, labels, test_mask)
|
||||
return acc
|
||||
@@ -0,0 +1,215 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.nn.pytorch as dglnn
|
||||
import torch as th
|
||||
import torch.multiprocessing as mp
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class SAGE(nn.Module):
|
||||
def __init__(
|
||||
self, in_feats, n_hidden, n_classes, n_layers, activation, dropout
|
||||
):
|
||||
super().__init__()
|
||||
self.n_layers = n_layers
|
||||
self.n_hidden = n_hidden
|
||||
self.n_classes = n_classes
|
||||
self.layers = nn.ModuleList()
|
||||
self.layers.append(dglnn.SAGEConv(in_feats, n_hidden, "mean"))
|
||||
for i in range(1, n_layers - 1):
|
||||
self.layers.append(dglnn.SAGEConv(n_hidden, n_hidden, "mean"))
|
||||
self.layers.append(dglnn.SAGEConv(n_hidden, n_classes, "mean"))
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.activation = activation
|
||||
|
||||
def forward(self, blocks, x):
|
||||
h = x
|
||||
for l, (layer, block) in enumerate(zip(self.layers, blocks)):
|
||||
h = layer(block, h)
|
||||
if l != len(self.layers) - 1:
|
||||
h = self.activation(h)
|
||||
h = self.dropout(h)
|
||||
return h
|
||||
|
||||
def inference(self, g, x, batch_size, device):
|
||||
"""
|
||||
Inference with the GraphSAGE model on full neighbors (i.e. without neighbor sampling).
|
||||
g : the entire graph.
|
||||
x : the input of entire node set.
|
||||
|
||||
The inference code is written in a fashion that it could handle any number of nodes and
|
||||
layers.
|
||||
"""
|
||||
# During inference with sampling, multi-layer blocks are very inefficient because
|
||||
# lots of computations in the first few layers are repeated.
|
||||
# Therefore, we compute the representation of all nodes layer by layer. The nodes
|
||||
# on each layer are of course splitted in batches.
|
||||
# TODO: can we standardize this?
|
||||
for l, layer in enumerate(self.layers):
|
||||
y = th.zeros(
|
||||
g.num_nodes(),
|
||||
self.n_hidden if l != len(self.layers) - 1 else self.n_classes,
|
||||
)
|
||||
|
||||
sampler = dgl.dataloading.MultiLayerFullNeighborSampler(1)
|
||||
dataloader = dgl.dataloading.DataLoader(
|
||||
g,
|
||||
th.arange(g.num_nodes()),
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=4,
|
||||
)
|
||||
|
||||
for input_nodes, output_nodes, blocks in dataloader:
|
||||
block = blocks[0]
|
||||
|
||||
block = block.int().to(device)
|
||||
h = x[input_nodes].to(device)
|
||||
h = layer(block, h)
|
||||
if l != len(self.layers) - 1:
|
||||
h = self.activation(h)
|
||||
h = self.dropout(h)
|
||||
|
||||
y[output_nodes] = h.cpu()
|
||||
|
||||
x = y
|
||||
return y
|
||||
|
||||
|
||||
def compute_acc(pred, labels):
|
||||
"""
|
||||
Compute the accuracy of prediction given the labels.
|
||||
"""
|
||||
labels = labels.long()
|
||||
return (th.argmax(pred, dim=1) == labels).float().sum() / len(pred)
|
||||
|
||||
|
||||
def evaluate(model, g, inputs, labels, val_nid, batch_size, device):
|
||||
"""
|
||||
Evaluate the model on the validation set specified by ``val_nid``.
|
||||
g : The entire graph.
|
||||
inputs : The features of all the nodes.
|
||||
labels : The labels of all the nodes.
|
||||
val_nid : the node Ids for validation.
|
||||
batch_size : Number of nodes to compute at the same time.
|
||||
device : The GPU device to evaluate on.
|
||||
"""
|
||||
model.eval()
|
||||
with th.no_grad():
|
||||
pred = model.inference(g, inputs, batch_size, device)
|
||||
model.train()
|
||||
return compute_acc(pred[val_nid], labels[val_nid])
|
||||
|
||||
|
||||
def load_subtensor(g, seeds, input_nodes, device):
|
||||
"""
|
||||
Copys features and labels of a set of nodes onto GPU.
|
||||
"""
|
||||
batch_inputs = g.ndata["features"][input_nodes].to(device)
|
||||
batch_labels = g.ndata["labels"][seeds].to(device)
|
||||
return batch_inputs, batch_labels
|
||||
|
||||
|
||||
@utils.benchmark("acc", 600)
|
||||
@utils.parametrize("data", ["ogbn-products", "reddit"])
|
||||
def track_acc(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
g = data[0]
|
||||
g.ndata["features"] = g.ndata["feat"]
|
||||
g.ndata["labels"] = g.ndata["label"]
|
||||
in_feats = g.ndata["features"].shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
# Create csr/coo/csc formats before launching training processes with multi-gpu.
|
||||
# This avoids creating certain formats in each sub-process, which saves momory and CPU.
|
||||
g.create_formats_()
|
||||
|
||||
num_epochs = 20
|
||||
num_hidden = 16
|
||||
num_layers = 2
|
||||
fan_out = "5,10"
|
||||
batch_size = 1024
|
||||
lr = 0.003
|
||||
dropout = 0.5
|
||||
num_workers = 4
|
||||
|
||||
train_nid = th.nonzero(g.ndata["train_mask"], as_tuple=True)[0]
|
||||
|
||||
# Create PyTorch DataLoader for constructing blocks
|
||||
sampler = dgl.dataloading.MultiLayerNeighborSampler(
|
||||
[int(fanout) for fanout in fan_out.split(",")]
|
||||
)
|
||||
dataloader = dgl.dataloading.DataLoader(
|
||||
g,
|
||||
train_nid,
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
# Define model and optimizer
|
||||
model = SAGE(in_feats, num_hidden, n_classes, num_layers, F.relu, dropout)
|
||||
model = model.to(device)
|
||||
loss_fcn = nn.CrossEntropyLoss()
|
||||
loss_fcn = loss_fcn.to(device)
|
||||
optimizer = optim.Adam(model.parameters(), lr=lr)
|
||||
|
||||
# dry run one epoch
|
||||
for step, (input_nodes, seeds, blocks) in enumerate(dataloader):
|
||||
# Load the input features as well as output labels
|
||||
# batch_inputs, batch_labels = load_subtensor(g, seeds, input_nodes, device)
|
||||
blocks = [block.int().to(device) for block in blocks]
|
||||
batch_inputs = blocks[0].srcdata["features"]
|
||||
batch_labels = blocks[-1].dstdata["labels"]
|
||||
|
||||
# Compute loss and prediction
|
||||
batch_pred = model(blocks, batch_inputs)
|
||||
loss = loss_fcn(batch_pred, batch_labels)
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# Training loop
|
||||
for epoch in range(num_epochs):
|
||||
# Loop over the dataloader to sample the computation dependency graph as a list of
|
||||
# blocks.
|
||||
for step, (input_nodes, seeds, blocks) in enumerate(dataloader):
|
||||
# Load the input features as well as output labels
|
||||
# batch_inputs, batch_labels = load_subtensor(g, seeds, input_nodes, device)
|
||||
blocks = [block.int().to(device) for block in blocks]
|
||||
batch_inputs = blocks[0].srcdata["features"]
|
||||
batch_labels = blocks[-1].dstdata["labels"]
|
||||
|
||||
# Compute loss and prediction
|
||||
batch_pred = model(blocks, batch_inputs)
|
||||
loss = loss_fcn(batch_pred, batch_labels)
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
test_g = g
|
||||
test_nid = th.nonzero(
|
||||
~(test_g.ndata["train_mask"] | test_g.ndata["val_mask"]), as_tuple=True
|
||||
)[0]
|
||||
test_acc = evaluate(
|
||||
model,
|
||||
test_g,
|
||||
test_g.ndata["features"],
|
||||
test_g.ndata["labels"],
|
||||
test_nid,
|
||||
batch_size,
|
||||
device,
|
||||
)
|
||||
|
||||
return test_acc.item()
|
||||
@@ -0,0 +1,131 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn.pytorch import GATConv
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class GAT(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
num_layers,
|
||||
in_dim,
|
||||
num_hidden,
|
||||
num_classes,
|
||||
heads,
|
||||
activation,
|
||||
feat_drop,
|
||||
attn_drop,
|
||||
negative_slope,
|
||||
residual,
|
||||
):
|
||||
super(GAT, self).__init__()
|
||||
self.num_layers = num_layers
|
||||
self.gat_layers = nn.ModuleList()
|
||||
self.activation = activation
|
||||
# input projection (no residual)
|
||||
self.gat_layers.append(
|
||||
GATConv(
|
||||
in_dim,
|
||||
num_hidden,
|
||||
heads[0],
|
||||
feat_drop,
|
||||
attn_drop,
|
||||
negative_slope,
|
||||
False,
|
||||
self.activation,
|
||||
)
|
||||
)
|
||||
# hidden layers
|
||||
for l in range(1, num_layers):
|
||||
# due to multi-head, the in_dim = num_hidden * num_heads
|
||||
self.gat_layers.append(
|
||||
GATConv(
|
||||
num_hidden * heads[l - 1],
|
||||
num_hidden,
|
||||
heads[l],
|
||||
feat_drop,
|
||||
attn_drop,
|
||||
negative_slope,
|
||||
residual,
|
||||
self.activation,
|
||||
)
|
||||
)
|
||||
# output projection
|
||||
self.gat_layers.append(
|
||||
GATConv(
|
||||
num_hidden * heads[-2],
|
||||
num_classes,
|
||||
heads[-1],
|
||||
feat_drop,
|
||||
attn_drop,
|
||||
negative_slope,
|
||||
residual,
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, g, inputs):
|
||||
h = inputs
|
||||
for l in range(self.num_layers):
|
||||
h = self.gat_layers[l](g, h).flatten(1)
|
||||
# output projection
|
||||
logits = self.gat_layers[-1](g, h).mean(1)
|
||||
return logits
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("data", ["cora", "pubmed"])
|
||||
def track_time(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
num_epochs = 200
|
||||
|
||||
g = data[0].to(device)
|
||||
|
||||
features = g.ndata["feat"]
|
||||
labels = g.ndata["label"]
|
||||
train_mask = g.ndata["train_mask"]
|
||||
val_mask = g.ndata["val_mask"]
|
||||
test_mask = g.ndata["test_mask"]
|
||||
|
||||
in_feats = features.shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
g = dgl.remove_self_loop(g)
|
||||
g = dgl.add_self_loop(g)
|
||||
|
||||
# create model
|
||||
model = GAT(1, in_feats, 8, n_classes, [8, 1], F.elu, 0.6, 0.6, 0.2, False)
|
||||
loss_fcn = torch.nn.CrossEntropyLoss()
|
||||
|
||||
model = model.to(device)
|
||||
model.train()
|
||||
|
||||
# optimizer
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
|
||||
|
||||
# dry run
|
||||
for epoch in range(10):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# timing
|
||||
t0 = time.time()
|
||||
for epoch in range(num_epochs):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / num_epochs
|
||||
@@ -0,0 +1,170 @@
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import dgl
|
||||
import dgl.nn.pytorch as dglnn
|
||||
import torch as th
|
||||
import torch.multiprocessing as mp
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class GAT(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_feats,
|
||||
num_heads,
|
||||
n_hidden,
|
||||
n_classes,
|
||||
n_layers,
|
||||
activation,
|
||||
dropout=0.0,
|
||||
):
|
||||
super().__init__()
|
||||
self.n_layers = n_layers
|
||||
self.n_hidden = n_hidden
|
||||
self.n_classes = n_classes
|
||||
self.layers = nn.ModuleList()
|
||||
self.num_heads = num_heads
|
||||
self.layers.append(
|
||||
dglnn.GATConv(
|
||||
in_feats,
|
||||
n_hidden,
|
||||
num_heads=num_heads,
|
||||
feat_drop=dropout,
|
||||
attn_drop=dropout,
|
||||
activation=activation,
|
||||
negative_slope=0.2,
|
||||
)
|
||||
)
|
||||
for i in range(1, n_layers - 1):
|
||||
self.layers.append(
|
||||
dglnn.GATConv(
|
||||
n_hidden * num_heads,
|
||||
n_hidden,
|
||||
num_heads=num_heads,
|
||||
feat_drop=dropout,
|
||||
attn_drop=dropout,
|
||||
activation=activation,
|
||||
negative_slope=0.2,
|
||||
)
|
||||
)
|
||||
self.layers.append(
|
||||
dglnn.GATConv(
|
||||
n_hidden * num_heads,
|
||||
n_classes,
|
||||
num_heads=num_heads,
|
||||
feat_drop=dropout,
|
||||
attn_drop=dropout,
|
||||
activation=None,
|
||||
negative_slope=0.2,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, blocks, x):
|
||||
h = x
|
||||
for l, (layer, block) in enumerate(zip(self.layers, blocks)):
|
||||
h = layer(block, h)
|
||||
if l < len(self.layers) - 1:
|
||||
h = h.flatten(1)
|
||||
h = h.mean(1)
|
||||
return h.log_softmax(dim=-1)
|
||||
|
||||
|
||||
def load_subtensor(g, seeds, input_nodes, device):
|
||||
"""
|
||||
Copys features and labels of a set of nodes onto GPU.
|
||||
"""
|
||||
batch_inputs = g.ndata["features"][input_nodes].to(device)
|
||||
batch_labels = g.ndata["labels"][seeds].to(device)
|
||||
return batch_inputs, batch_labels
|
||||
|
||||
|
||||
@utils.benchmark("time", 600)
|
||||
@utils.parametrize("data", ["reddit", "ogbn-products"])
|
||||
def track_time(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
g = data[0]
|
||||
g.ndata["features"] = g.ndata["feat"]
|
||||
g.ndata["labels"] = g.ndata["label"]
|
||||
g = g.remove_self_loop().add_self_loop()
|
||||
in_feats = g.ndata["features"].shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
# Create csr/coo/csc formats before launching training processes with multi-gpu.
|
||||
# This avoids creating certain formats in each sub-process, which saves momory and CPU.
|
||||
g.create_formats_()
|
||||
|
||||
num_hidden = 16
|
||||
num_heads = 8
|
||||
num_layers = 2
|
||||
fan_out = "10,25"
|
||||
batch_size = 1024
|
||||
lr = 0.003
|
||||
dropout = 0.5
|
||||
num_workers = 4
|
||||
iter_start = 3
|
||||
iter_count = 10
|
||||
|
||||
train_nid = th.nonzero(g.ndata["train_mask"], as_tuple=True)[0]
|
||||
|
||||
# Create PyTorch DataLoader for constructing blocks
|
||||
sampler = dgl.dataloading.MultiLayerNeighborSampler(
|
||||
[int(fanout) for fanout in fan_out.split(",")]
|
||||
)
|
||||
dataloader = dgl.dataloading.DataLoader(
|
||||
g,
|
||||
train_nid,
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
# Define model and optimizer
|
||||
model = GAT(
|
||||
in_feats, num_heads, num_hidden, n_classes, num_layers, F.relu, dropout
|
||||
)
|
||||
model = model.to(device)
|
||||
loss_fcn = nn.CrossEntropyLoss()
|
||||
loss_fcn = loss_fcn.to(device)
|
||||
optimizer = optim.Adam(model.parameters(), lr=lr)
|
||||
|
||||
# Enable dataloader cpu affinitization for cpu devices (no effect on gpu)
|
||||
with dataloader.enable_cpu_affinity():
|
||||
# Loop over the dataloader to sample the computation dependency graph as a list of
|
||||
# blocks.
|
||||
|
||||
# Training loop
|
||||
avg = 0
|
||||
iter_tput = []
|
||||
for step, (input_nodes, seeds, blocks) in enumerate(dataloader):
|
||||
# Load the input features as well as output labels
|
||||
blocks = [block.int().to(device) for block in blocks]
|
||||
batch_inputs = blocks[0].srcdata["features"]
|
||||
batch_labels = blocks[-1].dstdata["labels"]
|
||||
|
||||
# Compute loss and prediction
|
||||
batch_pred = model(blocks, batch_inputs)
|
||||
loss = loss_fcn(batch_pred, batch_labels)
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# start timer at before iter_start
|
||||
if step == iter_start - 1:
|
||||
t0 = time.time()
|
||||
elif (
|
||||
step == iter_count + iter_start - 1
|
||||
): # time iter_count iterations
|
||||
break
|
||||
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / iter_count
|
||||
@@ -0,0 +1,125 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class GraphConv(nn.Module):
|
||||
def __init__(self, in_dim, out_dim, activation=None):
|
||||
super(GraphConv, self).__init__()
|
||||
self.in_dim = in_dim
|
||||
self.out_dim = out_dim
|
||||
self.activation = activation
|
||||
self.weight = nn.Parameter(torch.Tensor(in_dim, out_dim))
|
||||
self.bias = nn.Parameter(torch.Tensor(out_dim))
|
||||
nn.init.xavier_normal_(self.weight)
|
||||
nn.init.zeros_(self.bias)
|
||||
|
||||
def forward(self, graph, feat):
|
||||
with graph.local_scope():
|
||||
graph.ndata["ci"] = torch.pow(
|
||||
graph.out_degrees().float().clamp(min=1), -0.5
|
||||
)
|
||||
graph.ndata["cj"] = torch.pow(
|
||||
graph.in_degrees().float().clamp(min=1), -0.5
|
||||
)
|
||||
graph.ndata["h"] = feat
|
||||
graph.update_all(self.mfunc, self.rfunc)
|
||||
h = graph.ndata["h"]
|
||||
h = torch.matmul(h, self.weight) + self.bias
|
||||
if self.activation is not None:
|
||||
h = self.activation(h)
|
||||
return h
|
||||
|
||||
def mfunc(self, edges):
|
||||
return {"m": edges.src["h"], "ci": edges.src["ci"]}
|
||||
|
||||
def rfunc(self, nodes):
|
||||
ci = nodes.mailbox["ci"].unsqueeze(2)
|
||||
newh = (nodes.mailbox["m"] * ci).sum(1) * nodes.data["cj"].unsqueeze(1)
|
||||
return {"h": newh}
|
||||
|
||||
|
||||
class GCN(nn.Module):
|
||||
def __init__(
|
||||
self, in_feats, n_hidden, n_classes, n_layers, activation, dropout
|
||||
):
|
||||
super(GCN, self).__init__()
|
||||
self.layers = nn.ModuleList()
|
||||
# input layer
|
||||
self.layers.append(GraphConv(in_feats, n_hidden, activation=activation))
|
||||
# hidden layers
|
||||
for i in range(n_layers - 1):
|
||||
self.layers.append(
|
||||
GraphConv(n_hidden, n_hidden, activation=activation)
|
||||
)
|
||||
# output layer
|
||||
self.layers.append(GraphConv(n_hidden, n_classes))
|
||||
self.dropout = nn.Dropout(p=dropout)
|
||||
|
||||
def forward(self, g, features):
|
||||
h = features
|
||||
for i, layer in enumerate(self.layers):
|
||||
if i != 0:
|
||||
h = self.dropout(h)
|
||||
h = layer(g, h)
|
||||
return h
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=300)
|
||||
@utils.parametrize("data", ["cora", "pubmed"])
|
||||
def track_time(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
g = data[0].to(device).int()
|
||||
|
||||
features = g.ndata["feat"]
|
||||
labels = g.ndata["label"]
|
||||
train_mask = g.ndata["train_mask"]
|
||||
val_mask = g.ndata["val_mask"]
|
||||
test_mask = g.ndata["test_mask"]
|
||||
|
||||
in_feats = features.shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
g = dgl.remove_self_loop(g)
|
||||
g = dgl.add_self_loop(g)
|
||||
|
||||
# normalization
|
||||
degs = g.in_degrees().float()
|
||||
norm = torch.pow(degs, -0.5)
|
||||
norm[torch.isinf(norm)] = 0
|
||||
g.ndata["norm"] = norm.unsqueeze(1)
|
||||
|
||||
# create GCN model
|
||||
model = GCN(in_feats, 16, n_classes, 1, F.relu, 0.5)
|
||||
loss_fcn = torch.nn.CrossEntropyLoss()
|
||||
|
||||
model = model.to(device)
|
||||
model.train()
|
||||
|
||||
# optimizer
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
|
||||
# dry run
|
||||
for epoch in range(5):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
with utils.Timer(device) as t:
|
||||
for epoch in range(200):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
return t.elapsed_secs / 200
|
||||
@@ -0,0 +1,501 @@
|
||||
import argparse
|
||||
import pickle
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torch.utils.data import DataLoader, IterableDataset
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
def _init_input_modules(g, ntype, textset, hidden_dims):
|
||||
# We initialize the linear projections of each input feature ``x`` as
|
||||
# follows:
|
||||
# * If ``x`` is a scalar integral feature, we assume that ``x`` is a categorical
|
||||
# feature, and assume the range of ``x`` is 0..max(x).
|
||||
# * If ``x`` is a float one-dimensional feature, we assume that ``x`` is a
|
||||
# numeric vector.
|
||||
# * If ``x`` is a field of a textset, we process it as bag of words.
|
||||
module_dict = nn.ModuleDict()
|
||||
|
||||
for column, data in g.nodes[ntype].data.items():
|
||||
if column == dgl.NID:
|
||||
continue
|
||||
if data.dtype == torch.float32:
|
||||
assert data.ndim == 2
|
||||
m = nn.Linear(data.shape[1], hidden_dims)
|
||||
nn.init.xavier_uniform_(m.weight)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
module_dict[column] = m
|
||||
elif data.dtype == torch.int64:
|
||||
assert data.ndim == 1
|
||||
m = nn.Embedding(data.max() + 2, hidden_dims, padding_idx=-1)
|
||||
nn.init.xavier_uniform_(m.weight)
|
||||
module_dict[column] = m
|
||||
|
||||
if textset is not None:
|
||||
for column, field in textset.fields.items():
|
||||
if field.vocab.vectors:
|
||||
module_dict[column] = BagOfWordsPretrained(field, hidden_dims)
|
||||
else:
|
||||
module_dict[column] = BagOfWords(field, hidden_dims)
|
||||
|
||||
return module_dict
|
||||
|
||||
|
||||
class BagOfWordsPretrained(nn.Module):
|
||||
def __init__(self, field, hidden_dims):
|
||||
super().__init__()
|
||||
|
||||
input_dims = field.vocab.vectors.shape[1]
|
||||
self.emb = nn.Embedding(
|
||||
len(field.vocab.itos),
|
||||
input_dims,
|
||||
padding_idx=field.vocab.stoi[field.pad_token],
|
||||
)
|
||||
self.emb.weight[:] = field.vocab.vectors
|
||||
self.proj = nn.Linear(input_dims, hidden_dims)
|
||||
nn.init.xavier_uniform_(self.proj.weight)
|
||||
nn.init.constant_(self.proj.bias, 0)
|
||||
|
||||
disable_grad(self.emb)
|
||||
|
||||
def forward(self, x, length):
|
||||
"""
|
||||
x: (batch_size, max_length) LongTensor
|
||||
length: (batch_size,) LongTensor
|
||||
"""
|
||||
x = self.emb(x).sum(1) / length.unsqueeze(1).float()
|
||||
return self.proj(x)
|
||||
|
||||
|
||||
class BagOfWords(nn.Module):
|
||||
def __init__(self, field, hidden_dims):
|
||||
super().__init__()
|
||||
|
||||
self.emb = nn.Embedding(
|
||||
len(field.vocab.itos),
|
||||
hidden_dims,
|
||||
padding_idx=field.vocab.stoi[field.pad_token],
|
||||
)
|
||||
nn.init.xavier_uniform_(self.emb.weight)
|
||||
|
||||
def forward(self, x, length):
|
||||
return self.emb(x).sum(1) / length.unsqueeze(1).float()
|
||||
|
||||
|
||||
class WeightedSAGEConv(nn.Module):
|
||||
def __init__(self, input_dims, hidden_dims, output_dims, act=F.relu):
|
||||
super().__init__()
|
||||
|
||||
self.act = act
|
||||
self.Q = nn.Linear(input_dims, hidden_dims)
|
||||
self.W = nn.Linear(input_dims + hidden_dims, output_dims)
|
||||
self.reset_parameters()
|
||||
self.dropout = nn.Dropout(0.5)
|
||||
|
||||
def reset_parameters(self):
|
||||
gain = nn.init.calculate_gain("relu")
|
||||
nn.init.xavier_uniform_(self.Q.weight, gain=gain)
|
||||
nn.init.xavier_uniform_(self.W.weight, gain=gain)
|
||||
nn.init.constant_(self.Q.bias, 0)
|
||||
nn.init.constant_(self.W.bias, 0)
|
||||
|
||||
def forward(self, g, h, weights):
|
||||
"""
|
||||
g : graph
|
||||
h : node features
|
||||
weights : scalar edge weights
|
||||
"""
|
||||
h_src, h_dst = h
|
||||
with g.local_scope():
|
||||
g.srcdata["n"] = self.act(self.Q(self.dropout(h_src)))
|
||||
g.edata["w"] = weights.float()
|
||||
g.update_all(fn.u_mul_e("n", "w", "m"), fn.sum("m", "n"))
|
||||
g.update_all(fn.copy_e("w", "m"), fn.sum("m", "ws"))
|
||||
n = g.dstdata["n"]
|
||||
ws = g.dstdata["ws"].unsqueeze(1).clamp(min=1)
|
||||
z = self.act(self.W(self.dropout(torch.cat([n / ws, h_dst], 1))))
|
||||
z_norm = z.norm(2, 1, keepdim=True)
|
||||
z_norm = torch.where(
|
||||
z_norm == 0, torch.tensor(1.0).to(z_norm), z_norm
|
||||
)
|
||||
z = z / z_norm
|
||||
return z
|
||||
|
||||
|
||||
class SAGENet(nn.Module):
|
||||
def __init__(self, hidden_dims, n_layers):
|
||||
"""
|
||||
g : DGLGraph
|
||||
The user-item interaction graph.
|
||||
This is only for finding the range of categorical variables.
|
||||
item_textsets : torchtext.data.Dataset
|
||||
The textual features of each item node.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.convs = nn.ModuleList()
|
||||
for _ in range(n_layers):
|
||||
self.convs.append(
|
||||
WeightedSAGEConv(hidden_dims, hidden_dims, hidden_dims)
|
||||
)
|
||||
|
||||
def forward(self, blocks, h):
|
||||
for layer, block in zip(self.convs, blocks):
|
||||
h_dst = h[: block.num_nodes("DST/" + block.ntypes[0])]
|
||||
h = layer(block, (h, h_dst), block.edata["weights"])
|
||||
return h
|
||||
|
||||
|
||||
class LinearProjector(nn.Module):
|
||||
"""
|
||||
Projects each input feature of the graph linearly and sums them up
|
||||
"""
|
||||
|
||||
def __init__(self, full_graph, ntype, textset, hidden_dims):
|
||||
super().__init__()
|
||||
|
||||
self.ntype = ntype
|
||||
self.inputs = _init_input_modules(
|
||||
full_graph, ntype, textset, hidden_dims
|
||||
)
|
||||
|
||||
def forward(self, ndata):
|
||||
projections = []
|
||||
for feature, data in ndata.items():
|
||||
if feature == dgl.NID or feature.endswith("__len"):
|
||||
# This is an additional feature indicating the length of the ``feature``
|
||||
# column; we shouldn't process this.
|
||||
continue
|
||||
|
||||
module = self.inputs[feature]
|
||||
if isinstance(module, (BagOfWords, BagOfWordsPretrained)):
|
||||
# Textual feature; find the length and pass it to the textual module.
|
||||
length = ndata[feature + "__len"]
|
||||
result = module(data, length)
|
||||
else:
|
||||
result = module(data)
|
||||
projections.append(result)
|
||||
|
||||
return torch.stack(projections, 1).sum(1)
|
||||
|
||||
|
||||
class ItemToItemScorer(nn.Module):
|
||||
def __init__(self, full_graph, ntype):
|
||||
super().__init__()
|
||||
|
||||
n_nodes = full_graph.num_nodes(ntype)
|
||||
self.bias = nn.Parameter(torch.zeros(n_nodes))
|
||||
|
||||
def _add_bias(self, edges):
|
||||
bias_src = self.bias[edges.src[dgl.NID]]
|
||||
bias_dst = self.bias[edges.dst[dgl.NID]]
|
||||
return {"s": edges.data["s"] + bias_src + bias_dst}
|
||||
|
||||
def forward(self, item_item_graph, h):
|
||||
"""
|
||||
item_item_graph : graph consists of edges connecting the pairs
|
||||
h : hidden state of every node
|
||||
"""
|
||||
with item_item_graph.local_scope():
|
||||
item_item_graph.ndata["h"] = h
|
||||
item_item_graph.apply_edges(fn.u_dot_v("h", "h", "s"))
|
||||
item_item_graph.apply_edges(self._add_bias)
|
||||
pair_score = item_item_graph.edata["s"]
|
||||
return pair_score
|
||||
|
||||
|
||||
class PinSAGEModel(nn.Module):
|
||||
def __init__(self, full_graph, ntype, textsets, hidden_dims, n_layers):
|
||||
super().__init__()
|
||||
|
||||
self.proj = LinearProjector(full_graph, ntype, textsets, hidden_dims)
|
||||
self.sage = SAGENet(hidden_dims, n_layers)
|
||||
self.scorer = ItemToItemScorer(full_graph, ntype)
|
||||
|
||||
def forward(self, pos_graph, neg_graph, blocks):
|
||||
h_item = self.get_repr(blocks)
|
||||
pos_score = self.scorer(pos_graph, h_item)
|
||||
neg_score = self.scorer(neg_graph, h_item)
|
||||
return (neg_score - pos_score + 1).clamp(min=0)
|
||||
|
||||
def get_repr(self, blocks):
|
||||
h_item = self.proj(blocks[0].srcdata)
|
||||
h_item_dst = self.proj(blocks[-1].dstdata)
|
||||
return h_item_dst + self.sage(blocks, h_item)
|
||||
|
||||
|
||||
def compact_and_copy(frontier, seeds):
|
||||
block = dgl.to_block(frontier, seeds)
|
||||
for col, data in frontier.edata.items():
|
||||
if col == dgl.EID:
|
||||
continue
|
||||
block.edata[col] = data[block.edata[dgl.EID]]
|
||||
return block
|
||||
|
||||
|
||||
class ItemToItemBatchSampler(IterableDataset):
|
||||
def __init__(self, g, user_type, item_type, batch_size):
|
||||
self.g = g
|
||||
self.user_type = user_type
|
||||
self.item_type = item_type
|
||||
self.user_to_item_etype = list(g.metagraph()[user_type][item_type])[0]
|
||||
self.item_to_user_etype = list(g.metagraph()[item_type][user_type])[0]
|
||||
self.batch_size = batch_size
|
||||
|
||||
def __iter__(self):
|
||||
while True:
|
||||
heads = torch.randint(
|
||||
0, self.g.num_nodes(self.item_type), (self.batch_size,)
|
||||
)
|
||||
tails = dgl.sampling.random_walk(
|
||||
self.g,
|
||||
heads,
|
||||
metapath=[self.item_to_user_etype, self.user_to_item_etype],
|
||||
)[0][:, 2]
|
||||
neg_tails = torch.randint(
|
||||
0, self.g.num_nodes(self.item_type), (self.batch_size,)
|
||||
)
|
||||
|
||||
mask = tails != -1
|
||||
yield heads[mask], tails[mask], neg_tails[mask]
|
||||
|
||||
|
||||
class NeighborSampler(object):
|
||||
def __init__(
|
||||
self,
|
||||
g,
|
||||
user_type,
|
||||
item_type,
|
||||
random_walk_length,
|
||||
random_walk_restart_prob,
|
||||
num_random_walks,
|
||||
num_neighbors,
|
||||
num_layers,
|
||||
):
|
||||
self.g = g
|
||||
self.user_type = user_type
|
||||
self.item_type = item_type
|
||||
self.user_to_item_etype = list(g.metagraph()[user_type][item_type])[0]
|
||||
self.item_to_user_etype = list(g.metagraph()[item_type][user_type])[0]
|
||||
self.samplers = [
|
||||
dgl.sampling.PinSAGESampler(
|
||||
g,
|
||||
item_type,
|
||||
user_type,
|
||||
random_walk_length,
|
||||
random_walk_restart_prob,
|
||||
num_random_walks,
|
||||
num_neighbors,
|
||||
)
|
||||
for _ in range(num_layers)
|
||||
]
|
||||
|
||||
def sample_blocks(self, seeds, heads=None, tails=None, neg_tails=None):
|
||||
blocks = []
|
||||
for sampler in self.samplers:
|
||||
frontier = sampler(seeds)
|
||||
if heads is not None:
|
||||
eids = frontier.edge_ids(
|
||||
torch.cat([heads, heads]),
|
||||
torch.cat([tails, neg_tails]),
|
||||
return_uv=True,
|
||||
)[2]
|
||||
if len(eids) > 0:
|
||||
old_frontier = frontier
|
||||
frontier = dgl.remove_edges(old_frontier, eids)
|
||||
# print(old_frontier)
|
||||
# print(frontier)
|
||||
# print(frontier.edata['weights'])
|
||||
# frontier.edata['weights'] = old_frontier.edata['weights'][frontier.edata[dgl.EID]]
|
||||
block = compact_and_copy(frontier, seeds)
|
||||
seeds = block.srcdata[dgl.NID]
|
||||
blocks.insert(0, block)
|
||||
return blocks
|
||||
|
||||
def sample_from_item_pairs(self, heads, tails, neg_tails):
|
||||
# Create a graph with positive connections only and another graph with negative
|
||||
# connections only.
|
||||
pos_graph = dgl.graph(
|
||||
(heads, tails), num_nodes=self.g.num_nodes(self.item_type)
|
||||
)
|
||||
neg_graph = dgl.graph(
|
||||
(heads, neg_tails), num_nodes=self.g.num_nodes(self.item_type)
|
||||
)
|
||||
pos_graph, neg_graph = dgl.compact_graphs([pos_graph, neg_graph])
|
||||
seeds = pos_graph.ndata[dgl.NID]
|
||||
|
||||
blocks = self.sample_blocks(seeds, heads, tails, neg_tails)
|
||||
return pos_graph, neg_graph, blocks
|
||||
|
||||
|
||||
def assign_simple_node_features(ndata, g, ntype, assign_id=False):
|
||||
"""
|
||||
Copies data to the given block from the corresponding nodes in the original graph.
|
||||
"""
|
||||
for col in g.nodes[ntype].data.keys():
|
||||
if not assign_id and col == dgl.NID:
|
||||
continue
|
||||
induced_nodes = ndata[dgl.NID]
|
||||
ndata[col] = g.nodes[ntype].data[col][induced_nodes]
|
||||
|
||||
|
||||
def assign_textual_node_features(ndata, textset, ntype):
|
||||
"""
|
||||
Assigns numericalized tokens from a torchtext dataset to given block.
|
||||
|
||||
The numericalized tokens would be stored in the block as node features
|
||||
with the same name as ``field_name``.
|
||||
|
||||
The length would be stored as another node feature with name
|
||||
``field_name + '__len'``.
|
||||
|
||||
block : DGLGraph
|
||||
First element of the compacted blocks, with "dgl.NID" as the
|
||||
corresponding node ID in the original graph, hence the index to the
|
||||
text dataset.
|
||||
|
||||
The numericalized tokens (and lengths if available) would be stored
|
||||
onto the blocks as new node features.
|
||||
textset : torchtext.data.Dataset
|
||||
A torchtext dataset whose number of examples is the same as that
|
||||
of nodes in the original graph.
|
||||
"""
|
||||
node_ids = ndata[dgl.NID].numpy()
|
||||
|
||||
for field_name, field in textset.fields.items():
|
||||
examples = [getattr(textset[i], field_name) for i in node_ids]
|
||||
|
||||
tokens, lengths = field.process(examples)
|
||||
|
||||
if not field.batch_first:
|
||||
tokens = tokens.t()
|
||||
|
||||
ndata[field_name] = tokens
|
||||
ndata[field_name + "__len"] = lengths
|
||||
|
||||
|
||||
def assign_features_to_blocks(blocks, g, textset, ntype):
|
||||
# For the first block (which is closest to the input), copy the features from
|
||||
# the original graph as well as the texts.
|
||||
assign_simple_node_features(blocks[0].srcdata, g, ntype)
|
||||
assign_textual_node_features(blocks[0].srcdata, textset, ntype)
|
||||
assign_simple_node_features(blocks[-1].dstdata, g, ntype)
|
||||
assign_textual_node_features(blocks[-1].dstdata, textset, ntype)
|
||||
|
||||
|
||||
class PinSAGECollator(object):
|
||||
def __init__(self, sampler, g, ntype, textset):
|
||||
self.sampler = sampler
|
||||
self.ntype = ntype
|
||||
self.g = g
|
||||
self.textset = textset
|
||||
|
||||
def collate_train(self, batches):
|
||||
heads, tails, neg_tails = batches[0]
|
||||
# Construct multilayer neighborhood via PinSAGE...
|
||||
pos_graph, neg_graph, blocks = self.sampler.sample_from_item_pairs(
|
||||
heads, tails, neg_tails
|
||||
)
|
||||
assign_features_to_blocks(blocks, self.g, self.textset, self.ntype)
|
||||
|
||||
return pos_graph, neg_graph, blocks
|
||||
|
||||
def collate_test(self, samples):
|
||||
batch = torch.LongTensor(samples)
|
||||
blocks = self.sampler.sample_blocks(batch)
|
||||
assign_features_to_blocks(blocks, self.g, self.textset, self.ntype)
|
||||
return blocks
|
||||
|
||||
|
||||
@utils.benchmark("time", 600)
|
||||
@utils.parametrize("data", ["nowplaying_rs"])
|
||||
def track_time(data):
|
||||
dataset = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
user_ntype = dataset.user_ntype
|
||||
item_ntype = dataset.item_ntype
|
||||
textset = dataset.textset
|
||||
|
||||
batch_size = 32
|
||||
random_walk_length = 2
|
||||
random_walk_restart_prob = 0.5
|
||||
num_random_walks = 10
|
||||
num_neighbors = 3
|
||||
num_layers = 2
|
||||
num_workers = 0
|
||||
hidden_dims = 16
|
||||
lr = 3e-5
|
||||
iter_start = 3
|
||||
iter_count = 10
|
||||
|
||||
g = dataset[0]
|
||||
# Sampler
|
||||
batch_sampler = ItemToItemBatchSampler(
|
||||
g, user_ntype, item_ntype, batch_size
|
||||
)
|
||||
neighbor_sampler = NeighborSampler(
|
||||
g,
|
||||
user_ntype,
|
||||
item_ntype,
|
||||
random_walk_length,
|
||||
random_walk_restart_prob,
|
||||
num_random_walks,
|
||||
num_neighbors,
|
||||
num_layers,
|
||||
)
|
||||
collator = PinSAGECollator(neighbor_sampler, g, item_ntype, textset)
|
||||
dataloader = DataLoader(
|
||||
batch_sampler,
|
||||
collate_fn=collator.collate_train,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
dataloader_test = DataLoader(
|
||||
torch.arange(g.num_nodes(item_ntype)),
|
||||
batch_size=batch_size,
|
||||
collate_fn=collator.collate_test,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
# Model
|
||||
model = PinSAGEModel(g, item_ntype, textset, hidden_dims, num_layers).to(
|
||||
device
|
||||
)
|
||||
# Optimizer
|
||||
opt = torch.optim.Adam(model.parameters(), lr=lr)
|
||||
|
||||
model.train()
|
||||
|
||||
print("start training...")
|
||||
# For each batch of head-tail-negative triplets...
|
||||
for batch_id, (pos_graph, neg_graph, blocks) in enumerate(dataloader):
|
||||
# Copy to GPU
|
||||
for i in range(len(blocks)):
|
||||
blocks[i] = blocks[i].to(device)
|
||||
pos_graph = pos_graph.to(device)
|
||||
neg_graph = neg_graph.to(device)
|
||||
|
||||
loss = model(pos_graph, neg_graph, blocks).mean()
|
||||
opt.zero_grad()
|
||||
loss.backward()
|
||||
opt.step()
|
||||
|
||||
# start timer at before iter_start
|
||||
if batch_id == iter_start - 1:
|
||||
t0 = time.time()
|
||||
elif (
|
||||
batch_id == iter_count + iter_start - 1
|
||||
): # time iter_count iterations
|
||||
break
|
||||
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / iter_count
|
||||
@@ -0,0 +1,58 @@
|
||||
import time
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from .. import rgcn, utils
|
||||
|
||||
|
||||
@utils.benchmark("time", 1200)
|
||||
@utils.parametrize("data", ["aifb", "am"])
|
||||
def track_time(data):
|
||||
# args
|
||||
if data == "aifb":
|
||||
num_bases = -1
|
||||
l2norm = 0.0
|
||||
elif data == "am":
|
||||
num_bases = 40
|
||||
l2norm = 5e-4
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
(
|
||||
g,
|
||||
num_rels,
|
||||
num_classes,
|
||||
labels,
|
||||
train_idx,
|
||||
test_idx,
|
||||
target_idx,
|
||||
) = rgcn.load_data(data, get_norm=True)
|
||||
num_hidden = 16
|
||||
|
||||
model = rgcn.RGCN(
|
||||
g.num_nodes(), num_hidden, num_classes, num_rels, num_bases=num_bases
|
||||
)
|
||||
device = utils.get_bench_device()
|
||||
labels = labels.to(device)
|
||||
model = model.to(device)
|
||||
g = g.int().to(device)
|
||||
|
||||
optimizer = torch.optim.Adam(
|
||||
model.parameters(), lr=1e-2, weight_decay=l2norm
|
||||
)
|
||||
|
||||
model.train()
|
||||
num_epochs = 30
|
||||
t0 = time.time()
|
||||
for epoch in range(num_epochs):
|
||||
logits = model(g)
|
||||
logits = logits[target_idx]
|
||||
loss = F.cross_entropy(logits[train_idx], labels[train_idx])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / num_epochs
|
||||
@@ -0,0 +1,390 @@
|
||||
import itertools
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import dgl
|
||||
import dgl.nn.pytorch as dglnn
|
||||
import torch as th
|
||||
import torch.multiprocessing as mp
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class RelGraphConvLayer(nn.Module):
|
||||
r"""Relational graph convolution layer.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
in_feat : int
|
||||
Input feature size.
|
||||
out_feat : int
|
||||
Output feature size.
|
||||
rel_names : list[str]
|
||||
Relation names.
|
||||
num_bases : int, optional
|
||||
Number of bases. If is none, use number of relations. Default: None.
|
||||
weight : bool, optional
|
||||
True if a linear layer is applied after message passing. Default: True
|
||||
bias : bool, optional
|
||||
True if bias is added. Default: True
|
||||
activation : callable, optional
|
||||
Activation function. Default: None
|
||||
self_loop : bool, optional
|
||||
True to include self loop message. Default: False
|
||||
dropout : float, optional
|
||||
Dropout rate. Default: 0.0
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_feat,
|
||||
out_feat,
|
||||
rel_names,
|
||||
num_bases,
|
||||
*,
|
||||
weight=True,
|
||||
bias=True,
|
||||
activation=None,
|
||||
self_loop=False,
|
||||
dropout=0.0
|
||||
):
|
||||
super(RelGraphConvLayer, self).__init__()
|
||||
self.in_feat = in_feat
|
||||
self.out_feat = out_feat
|
||||
self.rel_names = rel_names
|
||||
self.num_bases = num_bases
|
||||
self.bias = bias
|
||||
self.activation = activation
|
||||
self.self_loop = self_loop
|
||||
|
||||
self.conv = dglnn.HeteroGraphConv(
|
||||
{
|
||||
rel: dglnn.GraphConv(
|
||||
in_feat, out_feat, norm="right", weight=False, bias=False
|
||||
)
|
||||
for rel in rel_names
|
||||
}
|
||||
)
|
||||
|
||||
self.use_weight = weight
|
||||
self.use_basis = num_bases < len(self.rel_names) and weight
|
||||
if self.use_weight:
|
||||
if self.use_basis:
|
||||
self.basis = dglnn.WeightBasis(
|
||||
(in_feat, out_feat), num_bases, len(self.rel_names)
|
||||
)
|
||||
else:
|
||||
self.weight = nn.Parameter(
|
||||
th.Tensor(len(self.rel_names), in_feat, out_feat)
|
||||
)
|
||||
nn.init.xavier_uniform_(
|
||||
self.weight, gain=nn.init.calculate_gain("relu")
|
||||
)
|
||||
|
||||
# bias
|
||||
if bias:
|
||||
self.h_bias = nn.Parameter(th.Tensor(out_feat))
|
||||
nn.init.zeros_(self.h_bias)
|
||||
|
||||
# weight for self loop
|
||||
if self.self_loop:
|
||||
self.loop_weight = nn.Parameter(th.Tensor(in_feat, out_feat))
|
||||
nn.init.xavier_uniform_(
|
||||
self.loop_weight, gain=nn.init.calculate_gain("relu")
|
||||
)
|
||||
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
|
||||
def forward(self, g, inputs):
|
||||
"""Forward computation
|
||||
|
||||
Parameters
|
||||
----------
|
||||
g : DGLGraph
|
||||
Input graph.
|
||||
inputs : dict[str, torch.Tensor]
|
||||
Node feature for each node type.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict[str, torch.Tensor]
|
||||
New node features for each node type.
|
||||
"""
|
||||
g = g.local_var()
|
||||
if self.use_weight:
|
||||
weight = self.basis() if self.use_basis else self.weight
|
||||
wdict = {
|
||||
self.rel_names[i]: {"weight": w.squeeze(0)}
|
||||
for i, w in enumerate(th.split(weight, 1, dim=0))
|
||||
}
|
||||
else:
|
||||
wdict = {}
|
||||
|
||||
if g.is_block:
|
||||
inputs_src = inputs
|
||||
inputs_dst = {
|
||||
k: v[: g.number_of_dst_nodes(k)] for k, v in inputs.items()
|
||||
}
|
||||
else:
|
||||
inputs_src = inputs_dst = inputs
|
||||
|
||||
hs = self.conv(g, inputs, mod_kwargs=wdict)
|
||||
|
||||
def _apply(ntype, h):
|
||||
if self.self_loop:
|
||||
h = h + th.matmul(inputs_dst[ntype], self.loop_weight)
|
||||
if self.bias:
|
||||
h = h + self.h_bias
|
||||
if self.activation:
|
||||
h = self.activation(h)
|
||||
return self.dropout(h)
|
||||
|
||||
return {ntype: _apply(ntype, h) for ntype, h in hs.items()}
|
||||
|
||||
|
||||
class RelGraphEmbed(nn.Module):
|
||||
r"""Embedding layer for featureless heterograph."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
g,
|
||||
device,
|
||||
embed_size,
|
||||
num_nodes,
|
||||
node_feats,
|
||||
embed_name="embed",
|
||||
activation=None,
|
||||
dropout=0.0,
|
||||
):
|
||||
super(RelGraphEmbed, self).__init__()
|
||||
self.g = g
|
||||
self.device = device
|
||||
self.embed_size = embed_size
|
||||
self.embed_name = embed_name
|
||||
self.activation = activation
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.node_feats = node_feats
|
||||
|
||||
# create weight embeddings for each node for each relation
|
||||
self.embeds = nn.ParameterDict()
|
||||
self.node_embeds = nn.ModuleDict()
|
||||
for ntype in g.ntypes:
|
||||
if node_feats[ntype] is None:
|
||||
sparse_emb = th.nn.Embedding(
|
||||
num_nodes[ntype], embed_size, sparse=True
|
||||
)
|
||||
nn.init.uniform_(sparse_emb.weight, -1.0, 1.0)
|
||||
self.node_embeds[ntype] = sparse_emb
|
||||
else:
|
||||
input_emb_size = node_feats[ntype].shape[1]
|
||||
embed = nn.Parameter(th.Tensor(input_emb_size, embed_size))
|
||||
nn.init.xavier_uniform_(embed)
|
||||
self.embeds[ntype] = embed
|
||||
|
||||
def forward(self, block=None):
|
||||
"""Forward computation
|
||||
|
||||
Parameters
|
||||
----------
|
||||
block : DGLGraph, optional
|
||||
If not specified, directly return the full graph with embeddings stored in
|
||||
:attr:`embed_name`. Otherwise, extract and store the embeddings to the block
|
||||
graph and return.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DGLGraph
|
||||
The block graph fed with embeddings.
|
||||
"""
|
||||
embeds = {}
|
||||
for ntype in block.ntypes:
|
||||
if self.node_feats[ntype] is None:
|
||||
embeds[ntype] = self.node_embeds[ntype](block.nodes(ntype)).to(
|
||||
self.device
|
||||
)
|
||||
else:
|
||||
embeds[ntype] = (
|
||||
self.node_feats[ntype][block.nodes(ntype)].to(self.device)
|
||||
@ self.embeds[ntype]
|
||||
)
|
||||
return embeds
|
||||
|
||||
|
||||
class EntityClassify(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
g,
|
||||
h_dim,
|
||||
out_dim,
|
||||
num_bases,
|
||||
num_hidden_layers=1,
|
||||
dropout=0,
|
||||
use_self_loop=False,
|
||||
):
|
||||
super(EntityClassify, self).__init__()
|
||||
self.g = g
|
||||
self.h_dim = h_dim
|
||||
self.out_dim = out_dim
|
||||
self.rel_names = list(set(g.etypes))
|
||||
self.rel_names.sort()
|
||||
if num_bases < 0 or num_bases > len(self.rel_names):
|
||||
self.num_bases = len(self.rel_names)
|
||||
else:
|
||||
self.num_bases = num_bases
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.dropout = dropout
|
||||
self.use_self_loop = use_self_loop
|
||||
|
||||
self.layers = nn.ModuleList()
|
||||
# i2h
|
||||
self.layers.append(
|
||||
RelGraphConvLayer(
|
||||
self.h_dim,
|
||||
self.h_dim,
|
||||
self.rel_names,
|
||||
self.num_bases,
|
||||
activation=F.relu,
|
||||
self_loop=self.use_self_loop,
|
||||
dropout=self.dropout,
|
||||
weight=False,
|
||||
)
|
||||
)
|
||||
# h2h
|
||||
for i in range(self.num_hidden_layers):
|
||||
self.layers.append(
|
||||
RelGraphConvLayer(
|
||||
self.h_dim,
|
||||
self.h_dim,
|
||||
self.rel_names,
|
||||
self.num_bases,
|
||||
activation=F.relu,
|
||||
self_loop=self.use_self_loop,
|
||||
dropout=self.dropout,
|
||||
)
|
||||
)
|
||||
# h2o
|
||||
self.layers.append(
|
||||
RelGraphConvLayer(
|
||||
self.h_dim,
|
||||
self.out_dim,
|
||||
self.rel_names,
|
||||
self.num_bases,
|
||||
activation=None,
|
||||
self_loop=self.use_self_loop,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, h, blocks):
|
||||
for layer, block in zip(self.layers, blocks):
|
||||
h = layer(block, h)
|
||||
return h
|
||||
|
||||
|
||||
@utils.benchmark("time", 600)
|
||||
@utils.parametrize("data", ["ogbn-mag"])
|
||||
def track_time(data):
|
||||
dataset = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
if data == "ogbn-mag":
|
||||
n_bases = 2
|
||||
l2norm = 0
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
fanout = 4
|
||||
n_layers = 2
|
||||
batch_size = 1024
|
||||
n_hidden = 64
|
||||
dropout = 0.5
|
||||
use_self_loop = True
|
||||
lr = 0.01
|
||||
iter_start = 3
|
||||
iter_count = 10
|
||||
|
||||
hg = dataset[0]
|
||||
category = dataset.predict_category
|
||||
num_classes = dataset.num_classes
|
||||
train_mask = hg.nodes[category].data.pop("train_mask")
|
||||
train_idx = th.nonzero(train_mask, as_tuple=False).squeeze()
|
||||
labels = hg.nodes[category].data.pop("labels")
|
||||
|
||||
node_feats = {}
|
||||
num_nodes = {}
|
||||
for ntype in hg.ntypes:
|
||||
node_feats[ntype] = (
|
||||
hg.nodes[ntype].data["feat"]
|
||||
if "feat" in hg.nodes[ntype].data
|
||||
else None
|
||||
)
|
||||
num_nodes[ntype] = hg.num_nodes(ntype)
|
||||
|
||||
embed_layer = RelGraphEmbed(hg, device, n_hidden, num_nodes, node_feats)
|
||||
model = EntityClassify(
|
||||
hg,
|
||||
n_hidden,
|
||||
num_classes,
|
||||
num_bases=n_bases,
|
||||
num_hidden_layers=n_layers - 2,
|
||||
dropout=dropout,
|
||||
use_self_loop=use_self_loop,
|
||||
)
|
||||
embed_layer = embed_layer.to(device)
|
||||
model = model.to(device)
|
||||
|
||||
all_params = itertools.chain(
|
||||
model.parameters(), embed_layer.embeds.parameters()
|
||||
)
|
||||
optimizer = th.optim.Adam(all_params, lr=lr, weight_decay=l2norm)
|
||||
sparse_optimizer = th.optim.SparseAdam(
|
||||
list(embed_layer.node_embeds.parameters()), lr=lr
|
||||
)
|
||||
|
||||
sampler = dgl.dataloading.MultiLayerNeighborSampler([fanout] * n_layers)
|
||||
loader = dgl.dataloading.DataLoader(
|
||||
hg,
|
||||
{category: train_idx},
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
num_workers=4,
|
||||
)
|
||||
|
||||
print("start training...")
|
||||
model.train()
|
||||
embed_layer.train()
|
||||
optimizer.zero_grad()
|
||||
sparse_optimizer.zero_grad()
|
||||
|
||||
# Enable dataloader cpu affinitization for cpu devices (no effect on gpu)
|
||||
with loader.enable_cpu_affinity():
|
||||
for step, (input_nodes, seeds, blocks) in enumerate(loader):
|
||||
blocks = [blk.to(device) for blk in blocks]
|
||||
seeds = seeds[
|
||||
category
|
||||
] # we only predict the nodes with type "category"
|
||||
batch_tic = time.time()
|
||||
emb = embed_layer(blocks[0])
|
||||
lbl = labels[seeds].to(device)
|
||||
emb = {k: e.to(device) for k, e in emb.items()}
|
||||
logits = model(emb, blocks)[category]
|
||||
loss = F.cross_entropy(logits, lbl)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
sparse_optimizer.step()
|
||||
|
||||
# start timer at before iter_start
|
||||
if step == iter_start - 1:
|
||||
t0 = time.time()
|
||||
elif (
|
||||
step == iter_count + iter_start - 1
|
||||
): # time iter_count iterations
|
||||
break
|
||||
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / iter_count
|
||||
@@ -0,0 +1,368 @@
|
||||
import itertools
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.nn.pytorch as dglnn
|
||||
import torch as th
|
||||
import torch.multiprocessing as mp
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
from dgl.nn import RelGraphConv
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class EntityClassify(nn.Module):
|
||||
"""Entity classification class for RGCN
|
||||
Parameters
|
||||
----------
|
||||
device : int
|
||||
Device to run the layer.
|
||||
num_nodes : int
|
||||
Number of nodes.
|
||||
h_dim : int
|
||||
Hidden dim size.
|
||||
out_dim : int
|
||||
Output dim size.
|
||||
num_rels : int
|
||||
Numer of relation types.
|
||||
num_bases : int
|
||||
Number of bases. If is none, use number of relations.
|
||||
num_hidden_layers : int
|
||||
Number of hidden RelGraphConv Layer
|
||||
dropout : float
|
||||
Dropout
|
||||
use_self_loop : bool
|
||||
Use self loop if True, default False.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
num_nodes,
|
||||
h_dim,
|
||||
out_dim,
|
||||
num_rels,
|
||||
num_bases=None,
|
||||
num_hidden_layers=1,
|
||||
dropout=0,
|
||||
use_self_loop=False,
|
||||
layer_norm=False,
|
||||
):
|
||||
super(EntityClassify, self).__init__()
|
||||
self.device = device
|
||||
self.num_nodes = num_nodes
|
||||
self.h_dim = h_dim
|
||||
self.out_dim = out_dim
|
||||
self.num_rels = num_rels
|
||||
self.num_bases = None if num_bases < 0 else num_bases
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.dropout = dropout
|
||||
self.use_self_loop = use_self_loop
|
||||
self.layer_norm = layer_norm
|
||||
|
||||
self.layers = nn.ModuleList()
|
||||
# i2h
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.h_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=F.relu,
|
||||
self_loop=self.use_self_loop,
|
||||
dropout=self.dropout,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
# h2h
|
||||
for idx in range(self.num_hidden_layers):
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.h_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=F.relu,
|
||||
self_loop=self.use_self_loop,
|
||||
dropout=self.dropout,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
# h2o
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.out_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=None,
|
||||
self_loop=self.use_self_loop,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, blocks, feats, norm=None):
|
||||
if blocks is None:
|
||||
# full graph training
|
||||
blocks = [self.g] * len(self.layers)
|
||||
h = feats
|
||||
for layer, block in zip(self.layers, blocks):
|
||||
block = block.to(self.device)
|
||||
h = layer(block, h, block.edata["etype"], block.edata["norm"])
|
||||
return h
|
||||
|
||||
|
||||
class RelGraphEmbedLayer(nn.Module):
|
||||
r"""Embedding layer for featureless heterograph.
|
||||
Parameters
|
||||
----------
|
||||
device : int
|
||||
Device to run the layer.
|
||||
num_nodes : int
|
||||
Number of nodes.
|
||||
node_tides : tensor
|
||||
Storing the node type id for each node starting from 0
|
||||
num_of_ntype : int
|
||||
Number of node types
|
||||
input_size : list of int
|
||||
A list of input feature size for each node type. If None, we then
|
||||
treat certain input feature as an one-hot encoding feature.
|
||||
embed_size : int
|
||||
Output embed size
|
||||
embed_name : str, optional
|
||||
Embed name
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
num_nodes,
|
||||
node_tids,
|
||||
num_of_ntype,
|
||||
input_size,
|
||||
embed_size,
|
||||
sparse_emb=False,
|
||||
embed_name="embed",
|
||||
):
|
||||
super(RelGraphEmbedLayer, self).__init__()
|
||||
self.device = device
|
||||
self.embed_size = embed_size
|
||||
self.embed_name = embed_name
|
||||
self.num_nodes = num_nodes
|
||||
self.sparse_emb = sparse_emb
|
||||
|
||||
# create weight embeddings for each node for each relation
|
||||
self.embeds = nn.ParameterDict()
|
||||
self.num_of_ntype = num_of_ntype
|
||||
self.idmap = th.empty(num_nodes).long()
|
||||
|
||||
for ntype in range(num_of_ntype):
|
||||
if input_size[ntype] is not None:
|
||||
input_emb_size = input_size[ntype].shape[1]
|
||||
embed = nn.Parameter(th.Tensor(input_emb_size, self.embed_size))
|
||||
nn.init.xavier_uniform_(embed)
|
||||
self.embeds[str(ntype)] = embed
|
||||
|
||||
self.node_embeds = th.nn.Embedding(
|
||||
node_tids.shape[0], self.embed_size, sparse=self.sparse_emb
|
||||
)
|
||||
nn.init.uniform_(self.node_embeds.weight, -1.0, 1.0)
|
||||
|
||||
def forward(self, node_ids, node_tids, type_ids, features):
|
||||
"""Forward computation
|
||||
Parameters
|
||||
----------
|
||||
node_ids : tensor
|
||||
node ids to generate embedding for.
|
||||
node_tids : tensor
|
||||
node type ids
|
||||
features : list of features
|
||||
list of initial features for nodes belong to different node type.
|
||||
If None, the corresponding features is an one-hot encoding feature,
|
||||
else use the features directly as input feature and matmul a
|
||||
projection matrix.
|
||||
Returns
|
||||
-------
|
||||
tensor
|
||||
embeddings as the input of the next layer
|
||||
"""
|
||||
tsd_ids = node_ids.to(self.node_embeds.weight.device)
|
||||
embeds = th.empty(
|
||||
node_ids.shape[0], self.embed_size, device=self.device
|
||||
)
|
||||
for ntype in range(self.num_of_ntype):
|
||||
if features[ntype] is not None:
|
||||
loc = node_tids == ntype
|
||||
embeds[loc] = features[ntype][type_ids[loc]].to(
|
||||
self.device
|
||||
) @ self.embeds[str(ntype)].to(self.device)
|
||||
else:
|
||||
loc = node_tids == ntype
|
||||
embeds[loc] = self.node_embeds(tsd_ids[loc]).to(self.device)
|
||||
|
||||
return embeds
|
||||
|
||||
|
||||
@utils.benchmark("time", 600)
|
||||
@utils.parametrize("data", ["am", "ogbn-mag"])
|
||||
def track_time(data):
|
||||
dataset = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
|
||||
if data == "am":
|
||||
batch_size = 64
|
||||
n_bases = 40
|
||||
l2norm = 5e-4
|
||||
elif data == "ogbn-mag":
|
||||
batch_size = 1024
|
||||
n_bases = 2
|
||||
l2norm = 0
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
fanouts = [25, 15]
|
||||
n_layers = 2
|
||||
n_hidden = 64
|
||||
dropout = 0.5
|
||||
use_self_loop = True
|
||||
lr = 0.01
|
||||
num_workers = 4
|
||||
iter_start = 3
|
||||
iter_count = 10
|
||||
|
||||
hg = dataset[0]
|
||||
category = dataset.predict_category
|
||||
num_classes = dataset.num_classes
|
||||
train_mask = hg.nodes[category].data.pop("train_mask")
|
||||
train_idx = th.nonzero(train_mask, as_tuple=False).squeeze()
|
||||
labels = hg.nodes[category].data.pop("labels").to(device)
|
||||
num_of_ntype = len(hg.ntypes)
|
||||
num_rels = len(hg.canonical_etypes)
|
||||
|
||||
node_feats = []
|
||||
for ntype in hg.ntypes:
|
||||
if len(hg.nodes[ntype].data) == 0 or "feat" not in hg.nodes[ntype].data:
|
||||
node_feats.append(None)
|
||||
else:
|
||||
feat = hg.nodes[ntype].data.pop("feat")
|
||||
node_feats.append(feat.share_memory_())
|
||||
|
||||
# get target category id
|
||||
category_id = len(hg.ntypes)
|
||||
for i, ntype in enumerate(hg.ntypes):
|
||||
if ntype == category:
|
||||
category_id = i
|
||||
g = dgl.to_homogeneous(hg)
|
||||
u, v, eid = g.all_edges(form="all")
|
||||
|
||||
# global norm
|
||||
_, inverse_index, count = th.unique(
|
||||
v, return_inverse=True, return_counts=True
|
||||
)
|
||||
degrees = count[inverse_index]
|
||||
norm = th.ones(eid.shape[0]) / degrees
|
||||
norm = norm.unsqueeze(1)
|
||||
g.edata["norm"] = norm
|
||||
g.edata["etype"] = g.edata[dgl.ETYPE]
|
||||
g.ndata["type_id"] = g.ndata[dgl.NID]
|
||||
g.ndata["ntype"] = g.ndata[dgl.NTYPE]
|
||||
|
||||
node_ids = th.arange(g.num_nodes())
|
||||
# find out the target node ids
|
||||
node_tids = g.ndata[dgl.NTYPE]
|
||||
loc = node_tids == category_id
|
||||
target_nids = node_ids[loc]
|
||||
train_nids = target_nids[train_idx]
|
||||
|
||||
g = g.formats("csc")
|
||||
sampler = dgl.dataloading.MultiLayerNeighborSampler(fanouts)
|
||||
loader = dgl.dataloading.DataLoader(
|
||||
g,
|
||||
target_nids[train_idx],
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
# node features
|
||||
# None for one-hot feature, if not none, it should be the feature tensor.
|
||||
#
|
||||
embed_layer = RelGraphEmbedLayer(
|
||||
device,
|
||||
g.num_nodes(),
|
||||
node_tids,
|
||||
num_of_ntype,
|
||||
node_feats,
|
||||
n_hidden,
|
||||
sparse_emb=True,
|
||||
)
|
||||
|
||||
# create model
|
||||
# all model params are in device.
|
||||
model = EntityClassify(
|
||||
device,
|
||||
g.num_nodes(),
|
||||
n_hidden,
|
||||
num_classes,
|
||||
num_rels,
|
||||
num_bases=n_bases,
|
||||
num_hidden_layers=n_layers - 2,
|
||||
dropout=dropout,
|
||||
use_self_loop=use_self_loop,
|
||||
layer_norm=False,
|
||||
)
|
||||
|
||||
embed_layer = embed_layer.to(device)
|
||||
model = model.to(device)
|
||||
|
||||
all_params = itertools.chain(
|
||||
model.parameters(), embed_layer.embeds.parameters()
|
||||
)
|
||||
optimizer = th.optim.Adam(all_params, lr=lr, weight_decay=l2norm)
|
||||
emb_optimizer = th.optim.SparseAdam(
|
||||
list(embed_layer.node_embeds.parameters()), lr=lr
|
||||
)
|
||||
|
||||
print("start training...")
|
||||
model.train()
|
||||
embed_layer.train()
|
||||
|
||||
# Enable dataloader cpu affinitization for cpu devices (no effect on gpu)
|
||||
with loader.enable_cpu_affinity():
|
||||
for step, sample_data in enumerate(loader):
|
||||
input_nodes, output_nodes, blocks = sample_data
|
||||
feats = embed_layer(
|
||||
input_nodes,
|
||||
blocks[0].srcdata["ntype"],
|
||||
blocks[0].srcdata["type_id"],
|
||||
node_feats,
|
||||
)
|
||||
logits = model(blocks, feats)
|
||||
seed_idx = blocks[-1].dstdata["type_id"]
|
||||
loss = F.cross_entropy(logits, labels[seed_idx])
|
||||
optimizer.zero_grad()
|
||||
emb_optimizer.zero_grad()
|
||||
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
emb_optimizer.step()
|
||||
|
||||
# start timer at before iter_start
|
||||
if step == iter_start - 1:
|
||||
t0 = time.time()
|
||||
elif (
|
||||
step == iter_count + iter_start - 1
|
||||
): # time iter_count iterations
|
||||
break
|
||||
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / iter_count
|
||||
@@ -0,0 +1,98 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn.pytorch import SAGEConv
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class GraphSAGE(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_feats,
|
||||
n_hidden,
|
||||
n_classes,
|
||||
n_layers,
|
||||
activation,
|
||||
dropout,
|
||||
aggregator_type,
|
||||
):
|
||||
super(GraphSAGE, self).__init__()
|
||||
self.layers = nn.ModuleList()
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.activation = activation
|
||||
|
||||
# input layer
|
||||
self.layers.append(SAGEConv(in_feats, n_hidden, aggregator_type))
|
||||
# hidden layers
|
||||
for i in range(n_layers - 1):
|
||||
self.layers.append(SAGEConv(n_hidden, n_hidden, aggregator_type))
|
||||
# output layer
|
||||
self.layers.append(
|
||||
SAGEConv(n_hidden, n_classes, aggregator_type)
|
||||
) # activation None
|
||||
|
||||
def forward(self, graph, inputs):
|
||||
h = self.dropout(inputs)
|
||||
for l, layer in enumerate(self.layers):
|
||||
h = layer(graph, h)
|
||||
if l != len(self.layers) - 1:
|
||||
h = self.activation(h)
|
||||
h = self.dropout(h)
|
||||
return h
|
||||
|
||||
|
||||
@utils.benchmark("time")
|
||||
@utils.parametrize("data", ["cora", "pubmed"])
|
||||
def track_time(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
num_epochs = 200
|
||||
|
||||
g = data[0].to(device)
|
||||
|
||||
features = g.ndata["feat"]
|
||||
labels = g.ndata["label"]
|
||||
train_mask = g.ndata["train_mask"]
|
||||
val_mask = g.ndata["val_mask"]
|
||||
test_mask = g.ndata["test_mask"]
|
||||
|
||||
in_feats = features.shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
g = dgl.remove_self_loop(g)
|
||||
g = dgl.add_self_loop(g)
|
||||
|
||||
# create model
|
||||
model = GraphSAGE(in_feats, 16, n_classes, 1, F.relu, 0.5, "gcn")
|
||||
loss_fcn = torch.nn.CrossEntropyLoss()
|
||||
|
||||
model = model.to(device)
|
||||
model.train()
|
||||
|
||||
# optimizer
|
||||
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
|
||||
|
||||
# dry run
|
||||
for i in range(10):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# timing
|
||||
t0 = time.time()
|
||||
for epoch in range(num_epochs):
|
||||
logits = model(g, features)
|
||||
loss = loss_fcn(logits[train_mask], labels[train_mask])
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / num_epochs
|
||||
@@ -0,0 +1,129 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.nn.pytorch as dglnn
|
||||
import torch as th
|
||||
import torch.multiprocessing as mp
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class SAGE(nn.Module):
|
||||
def __init__(
|
||||
self, in_feats, n_hidden, n_classes, n_layers, activation, dropout
|
||||
):
|
||||
super().__init__()
|
||||
self.n_layers = n_layers
|
||||
self.n_hidden = n_hidden
|
||||
self.n_classes = n_classes
|
||||
self.layers = nn.ModuleList()
|
||||
self.layers.append(dglnn.SAGEConv(in_feats, n_hidden, "mean"))
|
||||
for i in range(1, n_layers - 1):
|
||||
self.layers.append(dglnn.SAGEConv(n_hidden, n_hidden, "mean"))
|
||||
self.layers.append(dglnn.SAGEConv(n_hidden, n_classes, "mean"))
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.activation = activation
|
||||
|
||||
def forward(self, blocks, x):
|
||||
h = x
|
||||
for l, (layer, block) in enumerate(zip(self.layers, blocks)):
|
||||
h = layer(block, h)
|
||||
if l != len(self.layers) - 1:
|
||||
h = self.activation(h)
|
||||
h = self.dropout(h)
|
||||
return h
|
||||
|
||||
|
||||
def load_subtensor(g, seeds, input_nodes, device):
|
||||
"""
|
||||
Copys features and labels of a set of nodes onto GPU.
|
||||
"""
|
||||
batch_inputs = g.ndata["features"][input_nodes].to(device)
|
||||
batch_labels = g.ndata["labels"][seeds].to(device)
|
||||
return batch_inputs, batch_labels
|
||||
|
||||
|
||||
@utils.benchmark("time", 600)
|
||||
@utils.parametrize("data", ["reddit", "ogbn-products"])
|
||||
def track_time(data):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
g = data[0]
|
||||
g.ndata["features"] = g.ndata["feat"]
|
||||
g.ndata["labels"] = g.ndata["label"]
|
||||
in_feats = g.ndata["features"].shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
# Create csr/coo/csc formats before launching training processes with multi-gpu.
|
||||
# This avoids creating certain formats in each sub-process, which saves momory and CPU.
|
||||
g.create_formats_()
|
||||
|
||||
num_epochs = 20
|
||||
num_hidden = 16
|
||||
num_layers = 2
|
||||
fan_out = "10,25"
|
||||
batch_size = 1024
|
||||
lr = 0.003
|
||||
dropout = 0.5
|
||||
num_workers = 4
|
||||
iter_start = 3
|
||||
iter_count = 10
|
||||
|
||||
train_nid = th.nonzero(g.ndata["train_mask"], as_tuple=True)[0]
|
||||
|
||||
# Create PyTorch DataLoader for constructing blocks
|
||||
sampler = dgl.dataloading.MultiLayerNeighborSampler(
|
||||
[int(fanout) for fanout in fan_out.split(",")]
|
||||
)
|
||||
dataloader = dgl.dataloading.DataLoader(
|
||||
g,
|
||||
train_nid,
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
# Define model and optimizer
|
||||
model = SAGE(in_feats, num_hidden, n_classes, num_layers, F.relu, dropout)
|
||||
model = model.to(device)
|
||||
loss_fcn = nn.CrossEntropyLoss()
|
||||
loss_fcn = loss_fcn.to(device)
|
||||
optimizer = optim.Adam(model.parameters(), lr=lr)
|
||||
|
||||
# Enable dataloader cpu affinitization for cpu devices (no effect on gpu)
|
||||
with dataloader.enable_cpu_affinity():
|
||||
# Training loop
|
||||
avg = 0
|
||||
iter_tput = []
|
||||
|
||||
for step, (input_nodes, seeds, blocks) in enumerate(dataloader):
|
||||
# Load the input features as well as output labels
|
||||
# batch_inputs, batch_labels = load_subtensor(g, seeds, input_nodes, device)
|
||||
blocks = [block.int().to(device) for block in blocks]
|
||||
batch_inputs = blocks[0].srcdata["features"]
|
||||
batch_labels = blocks[-1].dstdata["labels"]
|
||||
|
||||
# Compute loss and prediction
|
||||
batch_pred = model(blocks, batch_inputs)
|
||||
loss = loss_fcn(batch_pred, batch_labels)
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# start timer at before iter_start
|
||||
if step == iter_start - 1:
|
||||
t0 = time.time()
|
||||
elif (
|
||||
step == iter_count + iter_start - 1
|
||||
): # time iter_count iterations
|
||||
break
|
||||
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / iter_count
|
||||
@@ -0,0 +1,183 @@
|
||||
import time
|
||||
|
||||
import dgl
|
||||
import dgl.function as fn
|
||||
import dgl.nn.pytorch as dglnn
|
||||
|
||||
import numpy as np
|
||||
import torch as th
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class NegativeSampler(object):
|
||||
def __init__(self, g, k, neg_share=False):
|
||||
self.weights = g.in_degrees().float() ** 0.75
|
||||
self.k = k
|
||||
self.neg_share = neg_share
|
||||
|
||||
def __call__(self, g, eids):
|
||||
src, _ = g.find_edges(eids)
|
||||
n = len(src)
|
||||
if self.neg_share and n % self.k == 0:
|
||||
dst = self.weights.multinomial(n, replacement=True)
|
||||
dst = dst.view(-1, 1, self.k).expand(-1, self.k, -1).flatten()
|
||||
else:
|
||||
dst = self.weights.multinomial(n * self.k, replacement=True)
|
||||
src = src.repeat_interleave(self.k)
|
||||
return src, dst
|
||||
|
||||
|
||||
def load_subtensor(g, input_nodes, device):
|
||||
"""
|
||||
Copys features and labels of a set of nodes onto GPU.
|
||||
"""
|
||||
batch_inputs = g.ndata["features"][input_nodes].to(device)
|
||||
return batch_inputs
|
||||
|
||||
|
||||
class SAGE(nn.Module):
|
||||
def __init__(
|
||||
self, in_feats, n_hidden, n_classes, n_layers, activation, dropout
|
||||
):
|
||||
super().__init__()
|
||||
self.n_layers = n_layers
|
||||
self.n_hidden = n_hidden
|
||||
self.n_classes = n_classes
|
||||
self.layers = nn.ModuleList()
|
||||
self.layers.append(dglnn.SAGEConv(in_feats, n_hidden, "mean"))
|
||||
for i in range(1, n_layers - 1):
|
||||
self.layers.append(dglnn.SAGEConv(n_hidden, n_hidden, "mean"))
|
||||
self.layers.append(dglnn.SAGEConv(n_hidden, n_classes, "mean"))
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.activation = activation
|
||||
|
||||
def forward(self, blocks, x):
|
||||
h = x
|
||||
for l, (layer, block) in enumerate(zip(self.layers, blocks)):
|
||||
h = layer(block, h)
|
||||
if l != len(self.layers) - 1:
|
||||
h = self.activation(h)
|
||||
h = self.dropout(h)
|
||||
return h
|
||||
|
||||
|
||||
def load_subtensor(g, input_nodes, device):
|
||||
"""
|
||||
Copys features and labels of a set of nodes onto GPU.
|
||||
"""
|
||||
batch_inputs = g.ndata["features"][input_nodes].to(device)
|
||||
return batch_inputs
|
||||
|
||||
|
||||
class CrossEntropyLoss(nn.Module):
|
||||
def forward(self, block_outputs, pos_graph, neg_graph):
|
||||
with pos_graph.local_scope():
|
||||
pos_graph.ndata["h"] = block_outputs
|
||||
pos_graph.apply_edges(fn.u_dot_v("h", "h", "score"))
|
||||
pos_score = pos_graph.edata["score"]
|
||||
with neg_graph.local_scope():
|
||||
neg_graph.ndata["h"] = block_outputs
|
||||
neg_graph.apply_edges(fn.u_dot_v("h", "h", "score"))
|
||||
neg_score = neg_graph.edata["score"]
|
||||
|
||||
score = th.cat([pos_score, neg_score])
|
||||
label = th.cat(
|
||||
[th.ones_like(pos_score), th.zeros_like(neg_score)]
|
||||
).long()
|
||||
loss = F.binary_cross_entropy_with_logits(score, label.float())
|
||||
return loss
|
||||
|
||||
|
||||
@utils.benchmark("time", 600)
|
||||
@utils.parametrize("data", ["reddit"])
|
||||
@utils.parametrize("num_negs", [2, 8, 32])
|
||||
@utils.parametrize("batch_size", [1024, 2048, 8192])
|
||||
def track_time(data, num_negs, batch_size):
|
||||
data = utils.process_data(data)
|
||||
device = utils.get_bench_device()
|
||||
g = data[0]
|
||||
g.ndata["features"] = g.ndata["feat"]
|
||||
g.ndata["labels"] = g.ndata["label"]
|
||||
in_feats = g.ndata["features"].shape[1]
|
||||
n_classes = data.num_classes
|
||||
|
||||
# Create csr/coo/csc formats before launching training processes with multi-gpu.
|
||||
# This avoids creating certain formats in each sub-process, which saves momory and CPU.
|
||||
g.create_formats_()
|
||||
|
||||
num_epochs = 2
|
||||
num_hidden = 16
|
||||
num_layers = 2
|
||||
fan_out = "10,25"
|
||||
lr = 0.003
|
||||
dropout = 0.5
|
||||
num_workers = 4
|
||||
num_negs = 2
|
||||
iter_start = 3
|
||||
iter_count = 10
|
||||
|
||||
n_edges = g.num_edges()
|
||||
train_seeds = np.arange(n_edges)
|
||||
|
||||
# Create PyTorch DataLoader for constructing blocks
|
||||
sampler = dgl.dataloading.MultiLayerNeighborSampler(
|
||||
[int(fanout) for fanout in fan_out.split(",")]
|
||||
)
|
||||
sampler = dgl.dataloading.as_edge_prediction_sampler(
|
||||
sampler,
|
||||
exclude="reverse_id",
|
||||
# For each edge with ID e in Reddit dataset, the reverse edge is e ± |E|/2.
|
||||
reverse_eids=th.cat(
|
||||
[th.arange(n_edges // 2, n_edges), th.arange(0, n_edges // 2)]
|
||||
),
|
||||
negative_sampler=NegativeSampler(g, num_negs),
|
||||
)
|
||||
dataloader = dgl.dataloading.DataLoader(
|
||||
g,
|
||||
train_seeds,
|
||||
sampler,
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=num_workers,
|
||||
)
|
||||
|
||||
# Define model and optimizer
|
||||
model = SAGE(in_feats, num_hidden, n_classes, num_layers, F.relu, dropout)
|
||||
model = model.to(device)
|
||||
loss_fcn = CrossEntropyLoss()
|
||||
loss_fcn = loss_fcn.to(device)
|
||||
optimizer = optim.Adam(model.parameters(), lr=lr)
|
||||
|
||||
# Training loop
|
||||
avg = 0
|
||||
iter_tput = []
|
||||
for step, (input_nodes, pos_graph, neg_graph, blocks) in enumerate(
|
||||
dataloader
|
||||
):
|
||||
# Load the input features as well as output labels
|
||||
batch_inputs = load_subtensor(g, input_nodes, device)
|
||||
|
||||
pos_graph = pos_graph.to(device)
|
||||
neg_graph = neg_graph.to(device)
|
||||
blocks = [block.int().to(device) for block in blocks]
|
||||
# Compute loss and prediction
|
||||
batch_pred = model(blocks, batch_inputs)
|
||||
loss = loss_fcn(batch_pred, pos_graph, neg_graph)
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# start timer at before iter_start
|
||||
if step == iter_start - 1:
|
||||
t0 = time.time()
|
||||
elif step == iter_count + iter_start - 1: # time iter_count iterations
|
||||
break
|
||||
|
||||
t1 = time.time()
|
||||
|
||||
return (t1 - t0) / iter_count
|
||||
@@ -0,0 +1,565 @@
|
||||
"""
|
||||
Modeling Relational Data with Graph Convolutional Networks
|
||||
Paper: https://arxiv.org/abs/1703.06103
|
||||
Code: https://github.com/tkipf/relational-gcn
|
||||
Difference compared to tkipf/relation-gcn
|
||||
* l2norm applied to all weights
|
||||
* remove nodes that won't be touched
|
||||
"""
|
||||
import argparse
|
||||
import gc
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import torch as th
|
||||
import torch.multiprocessing as mp
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn import RelGraphConv
|
||||
from torch.multiprocessing import Queue
|
||||
from torch.nn.parallel import DistributedDataParallel
|
||||
from torch.utils.data import DataLoader
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class EntityClassify(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
num_nodes,
|
||||
h_dim,
|
||||
out_dim,
|
||||
num_rels,
|
||||
num_bases=None,
|
||||
num_hidden_layers=1,
|
||||
dropout=0,
|
||||
use_self_loop=False,
|
||||
layer_norm=False,
|
||||
):
|
||||
super(EntityClassify, self).__init__()
|
||||
self.device = th.device(device if device >= 0 else "cpu")
|
||||
self.num_nodes = num_nodes
|
||||
self.h_dim = h_dim
|
||||
self.out_dim = out_dim
|
||||
self.num_rels = num_rels
|
||||
self.num_bases = None if num_bases < 0 else num_bases
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.dropout = dropout
|
||||
self.use_self_loop = use_self_loop
|
||||
self.layer_norm = layer_norm
|
||||
|
||||
self.layers = nn.ModuleList()
|
||||
# i2h
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.h_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=F.relu,
|
||||
self_loop=self.use_self_loop,
|
||||
dropout=self.dropout,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
# h2h
|
||||
for idx in range(self.num_hidden_layers):
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.h_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=F.relu,
|
||||
self_loop=self.use_self_loop,
|
||||
dropout=self.dropout,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
# h2o
|
||||
self.layers.append(
|
||||
RelGraphConv(
|
||||
self.h_dim,
|
||||
self.out_dim,
|
||||
self.num_rels,
|
||||
"basis",
|
||||
self.num_bases,
|
||||
activation=None,
|
||||
self_loop=self.use_self_loop,
|
||||
layer_norm=layer_norm,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, blocks, feats, norm=None):
|
||||
if blocks is None:
|
||||
# full graph training
|
||||
blocks = [self.g] * len(self.layers)
|
||||
h = feats
|
||||
for layer, block in zip(self.layers, blocks):
|
||||
block = block.to(self.device)
|
||||
h = layer(block, h, block.edata["etype"], block.edata["norm"])
|
||||
return h
|
||||
|
||||
|
||||
def gen_norm(g):
|
||||
_, v, eid = g.all_edges(form="all")
|
||||
_, inverse_index, count = th.unique(
|
||||
v, return_inverse=True, return_counts=True
|
||||
)
|
||||
degrees = count[inverse_index]
|
||||
norm = th.ones(eid.shape[0], device=eid.device) / degrees
|
||||
norm = norm.unsqueeze(1)
|
||||
g.edata["norm"] = norm
|
||||
|
||||
|
||||
class NeighborSampler:
|
||||
def __init__(self, g, target_idx, fanouts):
|
||||
self.g = g
|
||||
self.target_idx = target_idx
|
||||
self.fanouts = fanouts
|
||||
|
||||
def sample_blocks(self, seeds):
|
||||
blocks = []
|
||||
etypes = []
|
||||
norms = []
|
||||
ntypes = []
|
||||
seeds = th.tensor(seeds).long()
|
||||
cur = self.target_idx[seeds]
|
||||
for fanout in self.fanouts:
|
||||
if fanout is None or fanout == -1:
|
||||
frontier = dgl.in_subgraph(self.g, cur)
|
||||
else:
|
||||
frontier = dgl.sampling.sample_neighbors(self.g, cur, fanout)
|
||||
block = dgl.to_block(frontier, cur)
|
||||
gen_norm(block)
|
||||
cur = block.srcdata[dgl.NID]
|
||||
blocks.insert(0, block)
|
||||
return seeds, blocks
|
||||
|
||||
|
||||
@utils.thread_wrapped_func
|
||||
def run(proc_id, n_gpus, n_cpus, args, devices, dataset, split, queue=None):
|
||||
from .rgcn_model import RelGraphEmbedLayer
|
||||
|
||||
dev_id = devices[proc_id]
|
||||
(
|
||||
g,
|
||||
node_feats,
|
||||
num_of_ntype,
|
||||
num_classes,
|
||||
num_rels,
|
||||
target_idx,
|
||||
train_idx,
|
||||
val_idx,
|
||||
test_idx,
|
||||
labels,
|
||||
) = dataset
|
||||
labels = labels.cuda(dev_id)
|
||||
if split is not None:
|
||||
train_seed, val_seed, test_seed = split
|
||||
train_idx = train_idx[train_seed]
|
||||
# val_idx = val_idx[val_seed]
|
||||
# test_idx = test_idx[test_seed]
|
||||
|
||||
fanouts = args.fanout
|
||||
node_tids = g.ndata[dgl.NTYPE]
|
||||
sampler = NeighborSampler(g, target_idx, fanouts)
|
||||
loader = DataLoader(
|
||||
dataset=train_idx.numpy(),
|
||||
batch_size=args.batch_size,
|
||||
collate_fn=sampler.sample_blocks,
|
||||
shuffle=True,
|
||||
num_workers=args.num_workers,
|
||||
)
|
||||
|
||||
world_size = n_gpus
|
||||
|
||||
dist_init_method = "tcp://{master_ip}:{master_port}".format(
|
||||
master_ip="127.0.0.1", master_port="12345"
|
||||
)
|
||||
backend = "nccl"
|
||||
|
||||
# using sparse embedding or usig mix_cpu_gpu model (embedding model can not be stored in GPU)
|
||||
if args.dgl_sparse is False:
|
||||
backend = "gloo"
|
||||
print("backend using {}".format(backend))
|
||||
th.distributed.init_process_group(
|
||||
backend=backend,
|
||||
init_method=dist_init_method,
|
||||
world_size=world_size,
|
||||
rank=dev_id,
|
||||
)
|
||||
|
||||
# node features
|
||||
# None for one-hot feature, if not none, it should be the feature tensor.
|
||||
#
|
||||
embed_layer = RelGraphEmbedLayer(
|
||||
dev_id,
|
||||
g.num_nodes(),
|
||||
node_tids,
|
||||
num_of_ntype,
|
||||
node_feats,
|
||||
args.n_hidden,
|
||||
dgl_sparse=args.dgl_sparse,
|
||||
)
|
||||
|
||||
# create model
|
||||
# all model params are in device.
|
||||
model = EntityClassify(
|
||||
dev_id,
|
||||
g.num_nodes(),
|
||||
args.n_hidden,
|
||||
num_classes,
|
||||
num_rels,
|
||||
num_bases=args.n_bases,
|
||||
num_hidden_layers=args.n_layers - 2,
|
||||
dropout=args.dropout,
|
||||
use_self_loop=args.use_self_loop,
|
||||
layer_norm=args.layer_norm,
|
||||
)
|
||||
|
||||
model.cuda(dev_id)
|
||||
model = DistributedDataParallel(
|
||||
model, device_ids=[dev_id], output_device=dev_id
|
||||
)
|
||||
if args.dgl_sparse:
|
||||
embed_layer.cuda(dev_id)
|
||||
if len(list(embed_layer.parameters())) > 0:
|
||||
embed_layer = DistributedDataParallel(
|
||||
embed_layer, device_ids=[dev_id], output_device=dev_id
|
||||
)
|
||||
else:
|
||||
if len(list(embed_layer.parameters())) > 0:
|
||||
embed_layer = DistributedDataParallel(
|
||||
embed_layer, device_ids=None, output_device=None
|
||||
)
|
||||
|
||||
# optimizer
|
||||
dense_params = list(model.parameters())
|
||||
if args.node_feats:
|
||||
if n_gpus > 1:
|
||||
dense_params += list(embed_layer.module.embeds.parameters())
|
||||
else:
|
||||
dense_params += list(embed_layer.embeds.parameters())
|
||||
optimizer = th.optim.Adam(
|
||||
dense_params, lr=args.lr, weight_decay=args.l2norm
|
||||
)
|
||||
|
||||
if args.dgl_sparse:
|
||||
all_params = list(model.parameters()) + list(embed_layer.parameters())
|
||||
optimizer = th.optim.Adam(
|
||||
all_params, lr=args.lr, weight_decay=args.l2norm
|
||||
)
|
||||
if n_gpus > 1 and isinstance(embed_layer, DistributedDataParallel):
|
||||
dgl_emb = embed_layer.module.dgl_emb
|
||||
else:
|
||||
dgl_emb = embed_layer.dgl_emb
|
||||
emb_optimizer = (
|
||||
dgl.optim.SparseAdam(params=dgl_emb, lr=args.sparse_lr, eps=1e-8)
|
||||
if len(dgl_emb) > 0
|
||||
else None
|
||||
)
|
||||
else:
|
||||
if n_gpus > 1:
|
||||
embs = list(embed_layer.module.node_embeds.parameters())
|
||||
else:
|
||||
embs = list(embed_layer.node_embeds.parameters())
|
||||
emb_optimizer = (
|
||||
th.optim.SparseAdam(embs, lr=args.sparse_lr)
|
||||
if len(embs) > 0
|
||||
else None
|
||||
)
|
||||
|
||||
# training loop
|
||||
print("start training...")
|
||||
forward_time = []
|
||||
backward_time = []
|
||||
|
||||
train_time = 0
|
||||
validation_time = 0
|
||||
test_time = 0
|
||||
last_val_acc = 0.0
|
||||
do_test = False
|
||||
if n_gpus > 1 and n_cpus - args.num_workers > 0:
|
||||
th.set_num_threads(n_cpus - args.num_workers)
|
||||
steps = 0
|
||||
time_records = []
|
||||
model.train()
|
||||
embed_layer.train()
|
||||
|
||||
# Warm up
|
||||
for i, sample_data in enumerate(loader):
|
||||
seeds, blocks = sample_data
|
||||
t0 = time.time()
|
||||
feats = embed_layer(
|
||||
blocks[0].srcdata[dgl.NID],
|
||||
blocks[0].srcdata["ntype"],
|
||||
blocks[0].srcdata["type_id"],
|
||||
node_feats,
|
||||
)
|
||||
logits = model(blocks, feats)
|
||||
loss = F.cross_entropy(logits, labels[seeds])
|
||||
t1 = time.time()
|
||||
optimizer.zero_grad()
|
||||
if emb_optimizer is not None:
|
||||
emb_optimizer.zero_grad()
|
||||
|
||||
loss.backward()
|
||||
if emb_optimizer is not None:
|
||||
emb_optimizer.step()
|
||||
optimizer.step()
|
||||
gc.collect()
|
||||
if i >= 3:
|
||||
break
|
||||
|
||||
# real time
|
||||
for i, sample_data in enumerate(loader):
|
||||
seeds, blocks = sample_data
|
||||
t0 = time.time()
|
||||
feats = embed_layer(
|
||||
blocks[0].srcdata[dgl.NID],
|
||||
blocks[0].srcdata["ntype"],
|
||||
blocks[0].srcdata["type_id"],
|
||||
node_feats,
|
||||
)
|
||||
logits = model(blocks, feats)
|
||||
loss = F.cross_entropy(logits, labels[seeds])
|
||||
t1 = time.time()
|
||||
optimizer.zero_grad()
|
||||
if emb_optimizer is not None:
|
||||
emb_optimizer.zero_grad()
|
||||
|
||||
loss.backward()
|
||||
if emb_optimizer is not None:
|
||||
emb_optimizer.step()
|
||||
optimizer.step()
|
||||
th.distributed.barrier()
|
||||
t2 = time.time()
|
||||
|
||||
forward_time.append(t1 - t0)
|
||||
backward_time.append(t2 - t1)
|
||||
time_records.append(t2 - t0)
|
||||
|
||||
gc.collect()
|
||||
if i >= 10:
|
||||
break
|
||||
|
||||
if proc_id == 0:
|
||||
queue.put(np.array(time_records))
|
||||
|
||||
|
||||
@utils.skip_if_not_4gpu()
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.parametrize("data", ["am", "ogbn-mag"])
|
||||
@utils.parametrize("dgl_sparse", [True, False])
|
||||
def track_time(data, dgl_sparse):
|
||||
# load graph data
|
||||
dataset = utils.process_data(data)
|
||||
args = config()
|
||||
devices = [0, 1, 2, 3]
|
||||
args.dgl_sparse = dgl_sparse
|
||||
args.dataset = dataset
|
||||
ogb_dataset = False
|
||||
|
||||
if data == "am":
|
||||
args.n_bases = 40
|
||||
args.l2norm = 5e-4
|
||||
elif data == "ogbn-mag":
|
||||
args.n_bases = 2
|
||||
args.l2norm = 0
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
if ogb_dataset is True:
|
||||
split_idx = dataset.get_idx_split()
|
||||
train_idx = split_idx["train"]["paper"]
|
||||
val_idx = split_idx["valid"]["paper"]
|
||||
test_idx = split_idx["test"]["paper"]
|
||||
hg_orig, labels = dataset[0]
|
||||
subgs = {}
|
||||
for etype in hg_orig.canonical_etypes:
|
||||
u, v = hg_orig.all_edges(etype=etype)
|
||||
subgs[etype] = (u, v)
|
||||
subgs[(etype[2], "rev-" + etype[1], etype[0])] = (v, u)
|
||||
hg = dgl.heterograph(subgs)
|
||||
hg.nodes["paper"].data["feat"] = hg_orig.nodes["paper"].data["feat"]
|
||||
labels = labels["paper"].squeeze()
|
||||
|
||||
num_rels = len(hg.canonical_etypes)
|
||||
num_of_ntype = len(hg.ntypes)
|
||||
num_classes = dataset.num_classes
|
||||
if args.dataset == "ogbn-mag":
|
||||
category = "paper"
|
||||
print("Number of relations: {}".format(num_rels))
|
||||
print("Number of class: {}".format(num_classes))
|
||||
print("Number of train: {}".format(len(train_idx)))
|
||||
print("Number of valid: {}".format(len(val_idx)))
|
||||
print("Number of test: {}".format(len(test_idx)))
|
||||
|
||||
else:
|
||||
# Load from hetero-graph
|
||||
hg = dataset[0]
|
||||
|
||||
num_rels = len(hg.canonical_etypes)
|
||||
num_of_ntype = len(hg.ntypes)
|
||||
category = dataset.predict_category
|
||||
num_classes = dataset.num_classes
|
||||
train_mask = hg.nodes[category].data.pop("train_mask")
|
||||
test_mask = hg.nodes[category].data.pop("test_mask")
|
||||
labels = hg.nodes[category].data.pop("labels")
|
||||
train_idx = th.nonzero(train_mask, as_tuple=False).squeeze()
|
||||
test_idx = th.nonzero(test_mask, as_tuple=False).squeeze()
|
||||
|
||||
# AIFB, MUTAG, BGS and AM datasets do not provide validation set split.
|
||||
# Split train set into train and validation if args.validation is set
|
||||
# otherwise use train set as the validation set.
|
||||
if args.validation:
|
||||
val_idx = train_idx[: len(train_idx) // 5]
|
||||
train_idx = train_idx[len(train_idx) // 5 :]
|
||||
else:
|
||||
val_idx = train_idx
|
||||
|
||||
node_feats = []
|
||||
for ntype in hg.ntypes:
|
||||
if len(hg.nodes[ntype].data) == 0 or args.node_feats is False:
|
||||
node_feats.append(hg.num_nodes(ntype))
|
||||
else:
|
||||
assert len(hg.nodes[ntype].data) == 1
|
||||
feat = hg.nodes[ntype].data.pop("feat")
|
||||
node_feats.append(feat.share_memory_())
|
||||
|
||||
# get target category id
|
||||
category_id = len(hg.ntypes)
|
||||
for i, ntype in enumerate(hg.ntypes):
|
||||
if ntype == category:
|
||||
category_id = i
|
||||
print("{}:{}".format(i, ntype))
|
||||
|
||||
g = dgl.to_homogeneous(hg)
|
||||
g.ndata["ntype"] = g.ndata[dgl.NTYPE]
|
||||
g.ndata["ntype"].share_memory_()
|
||||
g.edata["etype"] = g.edata[dgl.ETYPE]
|
||||
g.edata["etype"].share_memory_()
|
||||
g.ndata["type_id"] = g.ndata[dgl.NID]
|
||||
g.ndata["type_id"].share_memory_()
|
||||
node_ids = th.arange(g.num_nodes())
|
||||
|
||||
# find out the target node ids
|
||||
node_tids = g.ndata[dgl.NTYPE]
|
||||
loc = node_tids == category_id
|
||||
target_idx = node_ids[loc]
|
||||
target_idx.share_memory_()
|
||||
train_idx.share_memory_()
|
||||
val_idx.share_memory_()
|
||||
test_idx.share_memory_()
|
||||
# Create csr/coo/csc formats before launching training processes with multi-gpu.
|
||||
# This avoids creating certain formats in each sub-process, which saves momory and CPU.
|
||||
g.create_formats_()
|
||||
|
||||
n_gpus = len(devices)
|
||||
n_cpus = mp.cpu_count()
|
||||
|
||||
ctx = mp.get_context("spawn")
|
||||
queue = ctx.Queue()
|
||||
procs = []
|
||||
num_train_seeds = train_idx.shape[0]
|
||||
num_valid_seeds = val_idx.shape[0]
|
||||
num_test_seeds = test_idx.shape[0]
|
||||
train_seeds = th.randperm(num_train_seeds)
|
||||
valid_seeds = th.randperm(num_valid_seeds)
|
||||
test_seeds = th.randperm(num_test_seeds)
|
||||
tseeds_per_proc = num_train_seeds // n_gpus
|
||||
vseeds_per_proc = num_valid_seeds // n_gpus
|
||||
tstseeds_per_proc = num_test_seeds // n_gpus
|
||||
|
||||
for proc_id in range(n_gpus):
|
||||
# we have multi-gpu for training, evaluation and testing
|
||||
# so split trian set, valid set and test set into num-of-gpu parts.
|
||||
proc_train_seeds = train_seeds[
|
||||
proc_id * tseeds_per_proc : (proc_id + 1) * tseeds_per_proc
|
||||
if (proc_id + 1) * tseeds_per_proc < num_train_seeds
|
||||
else num_train_seeds
|
||||
]
|
||||
proc_valid_seeds = valid_seeds[
|
||||
proc_id * vseeds_per_proc : (proc_id + 1) * vseeds_per_proc
|
||||
if (proc_id + 1) * vseeds_per_proc < num_valid_seeds
|
||||
else num_valid_seeds
|
||||
]
|
||||
proc_test_seeds = test_seeds[
|
||||
proc_id * tstseeds_per_proc : (proc_id + 1) * tstseeds_per_proc
|
||||
if (proc_id + 1) * tstseeds_per_proc < num_test_seeds
|
||||
else num_test_seeds
|
||||
]
|
||||
|
||||
p = ctx.Process(
|
||||
target=run,
|
||||
args=(
|
||||
proc_id,
|
||||
n_gpus,
|
||||
n_cpus // n_gpus,
|
||||
args,
|
||||
devices,
|
||||
(
|
||||
g,
|
||||
node_feats,
|
||||
num_of_ntype,
|
||||
num_classes,
|
||||
num_rels,
|
||||
target_idx,
|
||||
train_idx,
|
||||
val_idx,
|
||||
test_idx,
|
||||
labels,
|
||||
),
|
||||
(proc_train_seeds, proc_valid_seeds, proc_test_seeds),
|
||||
queue,
|
||||
),
|
||||
)
|
||||
p.start()
|
||||
|
||||
procs.append(p)
|
||||
for p in procs:
|
||||
p.join()
|
||||
time_records = queue.get(block=False)
|
||||
num_exclude = 10 # exclude first 10 iterations
|
||||
if len(time_records) < 15:
|
||||
# exclude less if less records
|
||||
num_exclude = int(len(time_records) * 0.3)
|
||||
return np.mean(time_records[num_exclude:])
|
||||
|
||||
|
||||
def config():
|
||||
# parser = argparse.ArgumentParser(description='RGCN')
|
||||
args = SimpleNamespace(
|
||||
dropout=0,
|
||||
n_hidden=16,
|
||||
gpu="0,1,2,3",
|
||||
lr=1e-2,
|
||||
sparse_lr=2e-2,
|
||||
n_bases=-1,
|
||||
n_layers=2,
|
||||
dataset=None,
|
||||
l2norm=0,
|
||||
fanout=[10, 25],
|
||||
use_self_loop=True,
|
||||
batch_size=100,
|
||||
layer_norm=False,
|
||||
validation=False,
|
||||
node_feats=False,
|
||||
num_workers=0,
|
||||
dgl_sparse=False,
|
||||
)
|
||||
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
track_time("am")
|
||||
@@ -0,0 +1,193 @@
|
||||
import argparse
|
||||
import math
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
from typing import NamedTuple
|
||||
|
||||
import dgl
|
||||
import dgl.nn.pytorch as dglnn
|
||||
import numpy as np
|
||||
import torch as th
|
||||
import torch.multiprocessing as mp
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
from torch.nn.parallel import DistributedDataParallel
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
class SAGE(nn.Module):
|
||||
def __init__(
|
||||
self, in_feats, n_hidden, n_classes, n_layers, activation, dropout
|
||||
):
|
||||
super().__init__()
|
||||
self.n_layers = n_layers
|
||||
self.n_hidden = n_hidden
|
||||
self.n_classes = n_classes
|
||||
self.layers = nn.ModuleList()
|
||||
self.layers.append(dglnn.SAGEConv(in_feats, n_hidden, "mean"))
|
||||
for i in range(1, n_layers - 1):
|
||||
self.layers.append(dglnn.SAGEConv(n_hidden, n_hidden, "mean"))
|
||||
self.layers.append(dglnn.SAGEConv(n_hidden, n_classes, "mean"))
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.activation = activation
|
||||
|
||||
def forward(self, blocks, x):
|
||||
h = x
|
||||
for l, (layer, block) in enumerate(zip(self.layers, blocks)):
|
||||
h = layer(block, h)
|
||||
if l != len(self.layers) - 1:
|
||||
h = self.activation(h)
|
||||
h = self.dropout(h)
|
||||
return h
|
||||
|
||||
|
||||
def load_subtensor(nfeat, labels, seeds, input_nodes, dev_id):
|
||||
"""
|
||||
Extracts features and labels for a subset of nodes.
|
||||
"""
|
||||
batch_inputs = nfeat[input_nodes].to(dev_id)
|
||||
batch_labels = labels[seeds].to(dev_id)
|
||||
return batch_inputs, batch_labels
|
||||
|
||||
|
||||
# Entry point
|
||||
@utils.thread_wrapped_func
|
||||
def run(result_queue, proc_id, n_gpus, args, devices, data):
|
||||
dev_id = devices[proc_id]
|
||||
timing_records = []
|
||||
if n_gpus > 1:
|
||||
dist_init_method = "tcp://{master_ip}:{master_port}".format(
|
||||
master_ip="127.0.0.1", master_port="12345"
|
||||
)
|
||||
world_size = n_gpus
|
||||
th.distributed.init_process_group(
|
||||
backend="nccl",
|
||||
init_method=dist_init_method,
|
||||
world_size=world_size,
|
||||
rank=proc_id,
|
||||
)
|
||||
th.cuda.set_device(dev_id)
|
||||
|
||||
n_classes, train_g, _, _ = data
|
||||
|
||||
train_nfeat = train_g.ndata.pop("feat")
|
||||
train_labels = train_g.ndata.pop("label")
|
||||
|
||||
train_nfeat = train_nfeat.to(dev_id)
|
||||
train_labels = train_labels.to(dev_id)
|
||||
|
||||
in_feats = train_nfeat.shape[1]
|
||||
|
||||
train_mask = train_g.ndata["train_mask"]
|
||||
train_nid = train_mask.nonzero().squeeze()
|
||||
|
||||
# Split train_nid
|
||||
train_nid = th.split(train_nid, math.ceil(len(train_nid) / n_gpus))[proc_id]
|
||||
|
||||
# Create PyTorch DataLoader for constructing blocks
|
||||
sampler = dgl.dataloading.MultiLayerNeighborSampler(
|
||||
[int(fanout) for fanout in args.fan_out.split(",")]
|
||||
)
|
||||
dataloader = dgl.dataloading.DataLoader(
|
||||
train_g,
|
||||
train_nid,
|
||||
sampler,
|
||||
batch_size=args.batch_size,
|
||||
shuffle=True,
|
||||
drop_last=False,
|
||||
num_workers=args.num_workers,
|
||||
)
|
||||
|
||||
# Define model and optimizer
|
||||
model = SAGE(
|
||||
in_feats,
|
||||
args.num_hidden,
|
||||
n_classes,
|
||||
args.num_layers,
|
||||
F.relu,
|
||||
args.dropout,
|
||||
)
|
||||
model = model.to(dev_id)
|
||||
if n_gpus > 1:
|
||||
model = DistributedDataParallel(
|
||||
model, device_ids=[dev_id], output_device=dev_id
|
||||
)
|
||||
loss_fcn = nn.CrossEntropyLoss()
|
||||
optimizer = optim.Adam(model.parameters(), lr=args.lr)
|
||||
|
||||
# Training loop
|
||||
for step, (input_nodes, seeds, blocks) in enumerate(dataloader):
|
||||
if proc_id == 0:
|
||||
tic_step = time.time()
|
||||
|
||||
batch_inputs, batch_labels = load_subtensor(
|
||||
train_nfeat, train_labels, seeds, input_nodes, dev_id
|
||||
)
|
||||
blocks = [block.int().to(dev_id) for block in blocks]
|
||||
batch_pred = model(blocks, batch_inputs)
|
||||
loss = loss_fcn(batch_pred, batch_labels)
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
if proc_id == 0:
|
||||
timing_records.append(time.time() - tic_step)
|
||||
|
||||
if step >= 50:
|
||||
break
|
||||
|
||||
if n_gpus > 1:
|
||||
th.distributed.barrier()
|
||||
if proc_id == 0:
|
||||
result_queue.put(np.array(timing_records))
|
||||
|
||||
|
||||
@utils.benchmark("time", timeout=600)
|
||||
@utils.skip_if_not_4gpu()
|
||||
@utils.parametrize("data", ["reddit", "ogbn-products"])
|
||||
def track_time(data):
|
||||
args = SimpleNamespace(
|
||||
num_hidden=16,
|
||||
fan_out="10,25",
|
||||
batch_size=1000,
|
||||
lr=0.003,
|
||||
dropout=0.5,
|
||||
num_layers=2,
|
||||
num_workers=4,
|
||||
)
|
||||
|
||||
devices = [0, 1, 2, 3]
|
||||
n_gpus = len(devices)
|
||||
data = utils.process_data(data)
|
||||
g = data[0]
|
||||
n_classes = data.num_classes
|
||||
train_g = val_g = test_g = g
|
||||
|
||||
# Create csr/coo/csc formats before launching training processes with multi-gpu.
|
||||
# This avoids creating certain formats in each sub-process, which saves momory and CPU.
|
||||
train_g.create_formats_()
|
||||
val_g.create_formats_()
|
||||
test_g.create_formats_()
|
||||
# Pack data
|
||||
data = n_classes, train_g, val_g, test_g
|
||||
|
||||
ctx = mp.get_context("spawn")
|
||||
result_queue = ctx.Queue()
|
||||
procs = []
|
||||
for proc_id in range(n_gpus):
|
||||
p = ctx.Process(
|
||||
target=run,
|
||||
args=(result_queue, proc_id, n_gpus, args, devices, data),
|
||||
)
|
||||
p.start()
|
||||
procs.append(p)
|
||||
for p in procs:
|
||||
p.join()
|
||||
time_records = result_queue.get(block=False)
|
||||
num_exclude = 10 # exclude first 10 iterations
|
||||
if len(time_records) < 15:
|
||||
# exclude less if less records
|
||||
num_exclude = int(len(time_records) * 0.3)
|
||||
return np.mean(time_records[num_exclude:])
|
||||
@@ -0,0 +1,178 @@
|
||||
import dgl
|
||||
import torch as th
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class BaseRGCN(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
num_nodes,
|
||||
h_dim,
|
||||
out_dim,
|
||||
num_rels,
|
||||
num_bases,
|
||||
num_hidden_layers=1,
|
||||
dropout=0,
|
||||
use_self_loop=False,
|
||||
use_cuda=False,
|
||||
):
|
||||
super(BaseRGCN, self).__init__()
|
||||
self.num_nodes = num_nodes
|
||||
self.h_dim = h_dim
|
||||
self.out_dim = out_dim
|
||||
self.num_rels = num_rels
|
||||
self.num_bases = None if num_bases < 0 else num_bases
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.dropout = dropout
|
||||
self.use_self_loop = use_self_loop
|
||||
self.use_cuda = use_cuda
|
||||
|
||||
# create rgcn layers
|
||||
self.build_model()
|
||||
|
||||
def build_model(self):
|
||||
self.layers = nn.ModuleList()
|
||||
# i2h
|
||||
i2h = self.build_input_layer()
|
||||
if i2h is not None:
|
||||
self.layers.append(i2h)
|
||||
# h2h
|
||||
for idx in range(self.num_hidden_layers):
|
||||
h2h = self.build_hidden_layer(idx)
|
||||
self.layers.append(h2h)
|
||||
# h2o
|
||||
h2o = self.build_output_layer()
|
||||
if h2o is not None:
|
||||
self.layers.append(h2o)
|
||||
|
||||
def build_input_layer(self):
|
||||
return None
|
||||
|
||||
def build_hidden_layer(self, idx):
|
||||
raise NotImplementedError
|
||||
|
||||
def build_output_layer(self):
|
||||
return None
|
||||
|
||||
def forward(self, g, h, r, norm):
|
||||
for layer in self.layers:
|
||||
h = layer(g, h, r, norm)
|
||||
return h
|
||||
|
||||
|
||||
def initializer(emb):
|
||||
emb.uniform_(-1.0, 1.0)
|
||||
return emb
|
||||
|
||||
|
||||
class RelGraphEmbedLayer(nn.Module):
|
||||
r"""Embedding layer for featureless heterograph.
|
||||
Parameters
|
||||
----------
|
||||
dev_id : int
|
||||
Device to run the layer.
|
||||
num_nodes : int
|
||||
Number of nodes.
|
||||
node_tides : tensor
|
||||
Storing the node type id for each node starting from 0
|
||||
num_of_ntype : int
|
||||
Number of node types
|
||||
input_size : list of int
|
||||
A list of input feature size for each node type. If None, we then
|
||||
treat certain input feature as an one-hot encoding feature.
|
||||
embed_size : int
|
||||
Output embed size
|
||||
dgl_sparse : bool, optional
|
||||
If true, use dgl.nn.NodeEmbedding otherwise use torch.nn.Embedding
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dev_id,
|
||||
num_nodes,
|
||||
node_tids,
|
||||
num_of_ntype,
|
||||
input_size,
|
||||
embed_size,
|
||||
dgl_sparse=False,
|
||||
):
|
||||
super(RelGraphEmbedLayer, self).__init__()
|
||||
self.dev_id = th.device(dev_id if dev_id >= 0 else "cpu")
|
||||
self.embed_size = embed_size
|
||||
self.num_nodes = num_nodes
|
||||
self.dgl_sparse = dgl_sparse
|
||||
|
||||
# create weight embeddings for each node for each relation
|
||||
self.embeds = nn.ParameterDict()
|
||||
self.node_embeds = {} if dgl_sparse else nn.ModuleDict()
|
||||
self.num_of_ntype = num_of_ntype
|
||||
|
||||
for ntype in range(num_of_ntype):
|
||||
if isinstance(input_size[ntype], int):
|
||||
if dgl_sparse:
|
||||
self.node_embeds[str(ntype)] = dgl.nn.NodeEmbedding(
|
||||
input_size[ntype],
|
||||
embed_size,
|
||||
name=str(ntype),
|
||||
init_func=initializer,
|
||||
)
|
||||
else:
|
||||
sparse_emb = th.nn.Embedding(
|
||||
input_size[ntype], embed_size, sparse=True
|
||||
)
|
||||
nn.init.uniform_(sparse_emb.weight, -1.0, 1.0)
|
||||
self.node_embeds[str(ntype)] = sparse_emb
|
||||
else:
|
||||
input_emb_size = input_size[ntype].shape[1]
|
||||
embed = nn.Parameter(th.Tensor(input_emb_size, self.embed_size))
|
||||
nn.init.xavier_uniform_(embed)
|
||||
self.embeds[str(ntype)] = embed
|
||||
|
||||
@property
|
||||
def dgl_emb(self):
|
||||
""" """
|
||||
if self.dgl_sparse:
|
||||
embs = [emb for emb in self.node_embeds.values()]
|
||||
return embs
|
||||
else:
|
||||
return []
|
||||
|
||||
def forward(self, node_ids, node_tids, type_ids, features):
|
||||
"""Forward computation
|
||||
Parameters
|
||||
----------
|
||||
node_ids : tensor
|
||||
node ids to generate embedding for.
|
||||
node_ids : tensor
|
||||
node type ids
|
||||
features : list of features
|
||||
list of initial features for nodes belong to different node type.
|
||||
If None, the corresponding features is an one-hot encoding feature,
|
||||
else use the features directly as input feature and matmul a
|
||||
projection matrix.
|
||||
Returns
|
||||
-------
|
||||
tensor
|
||||
embeddings as the input of the next layer
|
||||
"""
|
||||
tsd_ids = node_ids.to(self.dev_id)
|
||||
embeds = th.empty(
|
||||
node_ids.shape[0], self.embed_size, device=self.dev_id
|
||||
)
|
||||
for ntype in range(self.num_of_ntype):
|
||||
loc = node_tids == ntype
|
||||
if isinstance(features[ntype], int):
|
||||
if self.dgl_sparse:
|
||||
embeds[loc] = self.node_embeds[str(ntype)](
|
||||
type_ids[loc], self.dev_id
|
||||
)
|
||||
else:
|
||||
embeds[loc] = self.node_embeds[str(ntype)](
|
||||
type_ids[loc]
|
||||
).to(self.dev_id)
|
||||
else:
|
||||
embeds[loc] = features[ntype][type_ids[loc]].to(
|
||||
self.dev_id
|
||||
) @ self.embeds[str(ntype)].to(self.dev_id)
|
||||
|
||||
return embeds
|
||||
@@ -0,0 +1,115 @@
|
||||
import dgl
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.nn.pytorch import RelGraphConv
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
class RGCN(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
num_nodes,
|
||||
h_dim,
|
||||
out_dim,
|
||||
num_rels,
|
||||
regularizer="basis",
|
||||
num_bases=-1,
|
||||
dropout=0.0,
|
||||
self_loop=False,
|
||||
ns_mode=False,
|
||||
):
|
||||
super(RGCN, self).__init__()
|
||||
|
||||
if num_bases == -1:
|
||||
num_bases = num_rels
|
||||
self.emb = nn.Embedding(num_nodes, h_dim)
|
||||
self.conv1 = RelGraphConv(
|
||||
h_dim, h_dim, num_rels, regularizer, num_bases, self_loop=self_loop
|
||||
)
|
||||
self.conv2 = RelGraphConv(
|
||||
h_dim,
|
||||
out_dim,
|
||||
num_rels,
|
||||
regularizer,
|
||||
num_bases,
|
||||
self_loop=self_loop,
|
||||
)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.ns_mode = ns_mode
|
||||
|
||||
def forward(self, g, nids=None):
|
||||
if self.ns_mode:
|
||||
# forward for neighbor sampling
|
||||
x = self.emb(g[0].srcdata[dgl.NID])
|
||||
h = self.conv1(g[0], x, g[0].edata[dgl.ETYPE], g[0].edata["norm"])
|
||||
h = self.dropout(F.relu(h))
|
||||
h = self.conv2(g[1], h, g[1].edata[dgl.ETYPE], g[1].edata["norm"])
|
||||
return h
|
||||
else:
|
||||
x = self.emb.weight if nids is None else self.emb(nids)
|
||||
h = self.conv1(g, x, g.edata[dgl.ETYPE], g.edata["norm"])
|
||||
h = self.dropout(F.relu(h))
|
||||
h = self.conv2(g, h, g.edata[dgl.ETYPE], g.edata["norm"])
|
||||
return h
|
||||
|
||||
|
||||
def load_data(data_name, get_norm=False, inv_target=False):
|
||||
dataset = utils.process_data(data_name)
|
||||
|
||||
# Load hetero-graph
|
||||
hg = dataset[0]
|
||||
|
||||
num_rels = len(hg.canonical_etypes)
|
||||
category = dataset.predict_category
|
||||
num_classes = dataset.num_classes
|
||||
labels = hg.nodes[category].data.pop("labels")
|
||||
train_mask = hg.nodes[category].data.pop("train_mask")
|
||||
test_mask = hg.nodes[category].data.pop("test_mask")
|
||||
train_idx = torch.nonzero(train_mask, as_tuple=False).squeeze()
|
||||
test_idx = torch.nonzero(test_mask, as_tuple=False).squeeze()
|
||||
|
||||
if get_norm:
|
||||
# Calculate normalization weight for each edge,
|
||||
# 1. / d, d is the degree of the destination node
|
||||
for cetype in hg.canonical_etypes:
|
||||
hg.edges[cetype].data["norm"] = dgl.norm_by_dst(
|
||||
hg, cetype
|
||||
).unsqueeze(1)
|
||||
edata = ["norm"]
|
||||
else:
|
||||
edata = None
|
||||
|
||||
# get target category id
|
||||
category_id = hg.ntypes.index(category)
|
||||
|
||||
g = dgl.to_homogeneous(hg, edata=edata)
|
||||
# Rename the fields as they can be changed by for example DataLoader
|
||||
g.ndata["ntype"] = g.ndata.pop(dgl.NTYPE)
|
||||
g.ndata["type_id"] = g.ndata.pop(dgl.NID)
|
||||
node_ids = torch.arange(g.num_nodes())
|
||||
|
||||
# find out the target node ids in g
|
||||
loc = g.ndata["ntype"] == category_id
|
||||
target_idx = node_ids[loc]
|
||||
|
||||
if inv_target:
|
||||
# Map global node IDs to type-specific node IDs. This is required for
|
||||
# looking up type-specific labels in a minibatch
|
||||
inv_target = torch.empty((g.num_nodes(),), dtype=torch.int64)
|
||||
inv_target[target_idx] = torch.arange(
|
||||
0, target_idx.shape[0], dtype=inv_target.dtype
|
||||
)
|
||||
return (
|
||||
g,
|
||||
num_rels,
|
||||
num_classes,
|
||||
labels,
|
||||
train_idx,
|
||||
test_idx,
|
||||
target_idx,
|
||||
inv_target,
|
||||
)
|
||||
else:
|
||||
return g, num_rels, num_classes, labels, train_idx, test_idx, target_idx
|
||||
@@ -0,0 +1,612 @@
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import shutil
|
||||
import time
|
||||
import zipfile
|
||||
from functools import partial, reduce, wraps
|
||||
from timeit import default_timer
|
||||
|
||||
import dgl
|
||||
|
||||
import numpy as np
|
||||
import pandas
|
||||
import requests
|
||||
import torch
|
||||
from ogb.nodeproppred import DglNodePropPredDataset
|
||||
|
||||
|
||||
def _download(url, path, filename):
|
||||
fn = os.path.join(path, filename)
|
||||
if os.path.exists(fn):
|
||||
return
|
||||
|
||||
os.makedirs(path, exist_ok=True)
|
||||
f_remote = requests.get(url, stream=True)
|
||||
sz = f_remote.headers.get("content-length")
|
||||
assert f_remote.status_code == 200, "fail to open {}".format(url)
|
||||
with open(fn, "wb") as writer:
|
||||
for chunk in f_remote.iter_content(chunk_size=1024 * 1024):
|
||||
writer.write(chunk)
|
||||
print("Download finished.")
|
||||
|
||||
|
||||
import traceback
|
||||
from _thread import start_new_thread
|
||||
|
||||
# GRAPH_CACHE = {}
|
||||
import torch.multiprocessing as mp
|
||||
|
||||
|
||||
def thread_wrapped_func(func):
|
||||
"""
|
||||
Wraps a process entry point to make it work with OpenMP.
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def decorated_function(*args, **kwargs):
|
||||
queue = mp.Queue()
|
||||
|
||||
def _queue_result():
|
||||
exception, trace, res = None, None, None
|
||||
try:
|
||||
res = func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
exception = e
|
||||
trace = traceback.format_exc()
|
||||
queue.put((res, exception, trace))
|
||||
|
||||
start_new_thread(_queue_result, ())
|
||||
result, exception, trace = queue.get()
|
||||
if exception is None:
|
||||
return result
|
||||
else:
|
||||
assert isinstance(exception, Exception)
|
||||
raise exception.__class__(trace)
|
||||
|
||||
return decorated_function
|
||||
|
||||
|
||||
def get_graph(name, format=None):
|
||||
# global GRAPH_CACHE
|
||||
# if name in GRAPH_CACHE:
|
||||
# return GRAPH_CACHE[name].to(format)
|
||||
if isinstance(format, str):
|
||||
format = [format] # didn't specify format
|
||||
if format is None:
|
||||
format = ["csc", "csr", "coo"]
|
||||
g = None
|
||||
if name == "cora":
|
||||
g = dgl.data.CoraGraphDataset(verbose=False)[0]
|
||||
elif name == "pubmed":
|
||||
g = dgl.data.PubmedGraphDataset(verbose=False)[0]
|
||||
elif name == "livejournal":
|
||||
bin_path = "/tmp/dataset/livejournal/livejournal_{}.bin".format(format)
|
||||
if os.path.exists(bin_path):
|
||||
g_list, _ = dgl.load_graphs(bin_path)
|
||||
g = g_list[0]
|
||||
else:
|
||||
g = get_livejournal().formats(format)
|
||||
dgl.save_graphs(bin_path, [g])
|
||||
elif name == "friendster":
|
||||
bin_path = "/tmp/dataset/friendster/friendster_{}.bin".format(format)
|
||||
if os.path.exists(bin_path):
|
||||
g_list, _ = dgl.load_graphs(bin_path)
|
||||
g = g_list[0]
|
||||
else:
|
||||
# the original node IDs of friendster are not consecutive, so we compact it
|
||||
g = dgl.compact_graphs(get_friendster()).formats(format)
|
||||
dgl.save_graphs(bin_path, [g])
|
||||
elif name == "reddit":
|
||||
bin_path = "/tmp/dataset/reddit/reddit_{}.bin".format(format)
|
||||
if os.path.exists(bin_path):
|
||||
g_list, _ = dgl.load_graphs(bin_path)
|
||||
g = g_list[0]
|
||||
else:
|
||||
g = dgl.data.RedditDataset(self_loop=True)[0].formats(format)
|
||||
dgl.save_graphs(bin_path, [g])
|
||||
elif name.startswith("ogb"):
|
||||
g = get_ogb_graph(name)
|
||||
else:
|
||||
raise Exception("Unknown dataset")
|
||||
# GRAPH_CACHE[name] = g
|
||||
g = g.formats(format)
|
||||
return g
|
||||
|
||||
|
||||
def get_ogb_graph(name):
|
||||
os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
|
||||
data = DglNodePropPredDataset(name=name)
|
||||
return data[0][0]
|
||||
|
||||
|
||||
def get_livejournal():
|
||||
# Same as https://snap.stanford.edu/data/soc-LiveJournal1.txt.gz
|
||||
_download(
|
||||
"https://dgl-asv-data.s3-us-west-2.amazonaws.com/dataset/livejournal/soc-LiveJournal1.txt.gz",
|
||||
"/tmp/dataset/livejournal",
|
||||
"soc-LiveJournal1.txt.gz",
|
||||
)
|
||||
df = pandas.read_csv(
|
||||
"/tmp/dataset/livejournal/soc-LiveJournal1.txt.gz",
|
||||
sep="\t",
|
||||
skiprows=4,
|
||||
header=None,
|
||||
names=["src", "dst"],
|
||||
compression="gzip",
|
||||
)
|
||||
src = df["src"].values
|
||||
dst = df["dst"].values
|
||||
print("construct the graph")
|
||||
return dgl.graph((src, dst))
|
||||
|
||||
|
||||
def get_friendster():
|
||||
# Same as https://snap.stanford.edu/data/bigdata/communities/com-friendster.ungraph.txt.gz
|
||||
_download(
|
||||
"https://dgl-asv-data.s3-us-west-2.amazonaws.com/dataset/friendster/com-friendster.ungraph.txt.gz",
|
||||
"/tmp/dataset/friendster",
|
||||
"com-friendster.ungraph.txt.gz",
|
||||
)
|
||||
df = pandas.read_csv(
|
||||
"/tmp/dataset/friendster/com-friendster.ungraph.txt.gz",
|
||||
sep="\t",
|
||||
skiprows=4,
|
||||
header=None,
|
||||
names=["src", "dst"],
|
||||
compression="gzip",
|
||||
)
|
||||
src = df["src"].values
|
||||
dst = df["dst"].values
|
||||
print("construct the graph")
|
||||
return dgl.graph((src, dst))
|
||||
|
||||
|
||||
class OGBDataset(object):
|
||||
def __init__(self, g, num_labels, predict_category=None):
|
||||
self._g = g
|
||||
self._num_labels = num_labels
|
||||
self._predict_category = predict_category
|
||||
|
||||
@property
|
||||
def num_labels(self):
|
||||
return self._num_labels
|
||||
|
||||
@property
|
||||
def num_classes(self):
|
||||
return self._num_labels
|
||||
|
||||
@property
|
||||
def predict_category(self):
|
||||
return self._predict_category
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return self._g
|
||||
|
||||
|
||||
def load_ogb_product():
|
||||
name = "ogbn-products"
|
||||
os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
|
||||
|
||||
print("load", name)
|
||||
data = DglNodePropPredDataset(name=name)
|
||||
print("finish loading", name)
|
||||
splitted_idx = data.get_idx_split()
|
||||
graph, labels = data[0]
|
||||
labels = labels[:, 0]
|
||||
|
||||
graph.ndata["label"] = labels
|
||||
in_feats = graph.ndata["feat"].shape[1]
|
||||
num_labels = len(
|
||||
torch.unique(labels[torch.logical_not(torch.isnan(labels))])
|
||||
)
|
||||
|
||||
# Find the node IDs in the training, validation, and test set.
|
||||
train_nid, val_nid, test_nid = (
|
||||
splitted_idx["train"],
|
||||
splitted_idx["valid"],
|
||||
splitted_idx["test"],
|
||||
)
|
||||
train_mask = torch.zeros((graph.num_nodes(),), dtype=torch.bool)
|
||||
train_mask[train_nid] = True
|
||||
val_mask = torch.zeros((graph.num_nodes(),), dtype=torch.bool)
|
||||
val_mask[val_nid] = True
|
||||
test_mask = torch.zeros((graph.num_nodes(),), dtype=torch.bool)
|
||||
test_mask[test_nid] = True
|
||||
graph.ndata["train_mask"] = train_mask
|
||||
graph.ndata["val_mask"] = val_mask
|
||||
graph.ndata["test_mask"] = test_mask
|
||||
|
||||
return OGBDataset(graph, num_labels)
|
||||
|
||||
|
||||
def load_ogb_mag():
|
||||
name = "ogbn-mag"
|
||||
os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
|
||||
|
||||
print("load", name)
|
||||
dataset = DglNodePropPredDataset(name=name)
|
||||
print("finish loading", name)
|
||||
split_idx = dataset.get_idx_split()
|
||||
train_idx = split_idx["train"]["paper"]
|
||||
val_idx = split_idx["valid"]["paper"]
|
||||
test_idx = split_idx["test"]["paper"]
|
||||
hg_orig, labels = dataset[0]
|
||||
subgs = {}
|
||||
for etype in hg_orig.canonical_etypes:
|
||||
u, v = hg_orig.all_edges(etype=etype)
|
||||
subgs[etype] = (u, v)
|
||||
subgs[(etype[2], "rev-" + etype[1], etype[0])] = (v, u)
|
||||
hg = dgl.heterograph(subgs)
|
||||
hg.nodes["paper"].data["feat"] = hg_orig.nodes["paper"].data["feat"]
|
||||
hg.nodes["paper"].data["labels"] = labels["paper"].squeeze()
|
||||
train_mask = torch.zeros((hg.num_nodes("paper"),), dtype=torch.bool)
|
||||
train_mask[train_idx] = True
|
||||
val_mask = torch.zeros((hg.num_nodes("paper"),), dtype=torch.bool)
|
||||
val_mask[val_idx] = True
|
||||
test_mask = torch.zeros((hg.num_nodes("paper"),), dtype=torch.bool)
|
||||
test_mask[test_idx] = True
|
||||
hg.nodes["paper"].data["train_mask"] = train_mask
|
||||
hg.nodes["paper"].data["val_mask"] = val_mask
|
||||
hg.nodes["paper"].data["test_mask"] = test_mask
|
||||
|
||||
num_classes = dataset.num_classes
|
||||
return OGBDataset(hg, num_classes, "paper")
|
||||
|
||||
|
||||
class PinsageDataset:
|
||||
def __init__(self, g, user_ntype, item_ntype, textset):
|
||||
self._g = g
|
||||
self._user_ntype = user_ntype
|
||||
self._item_ntype = item_ntype
|
||||
self._textset = textset
|
||||
|
||||
@property
|
||||
def user_ntype(self):
|
||||
return self._user_ntype
|
||||
|
||||
@property
|
||||
def item_ntype(self):
|
||||
return self._item_ntype
|
||||
|
||||
@property
|
||||
def textset(self):
|
||||
return self._textset
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return self._g
|
||||
|
||||
|
||||
def load_nowplaying_rs():
|
||||
import torchtext.legacy as torchtext
|
||||
|
||||
# follow examples/pytorch/pinsage/README to create train_g.bin
|
||||
name = "train_g.bin"
|
||||
dataset_dir = os.path.join(os.getcwd(), "dataset")
|
||||
os.symlink("/tmp/dataset/", dataset_dir)
|
||||
|
||||
dataset_path = os.path.join(dataset_dir, "nowplaying_rs", name)
|
||||
g_list, _ = dgl.load_graphs(dataset_path)
|
||||
g = g_list[0]
|
||||
user_ntype = "user"
|
||||
item_ntype = "track"
|
||||
|
||||
# Assign user and movie IDs and use them as features (to learn an individual trainable
|
||||
# embedding for each entity)
|
||||
g.nodes[user_ntype].data["id"] = torch.arange(g.num_nodes(user_ntype))
|
||||
g.nodes[item_ntype].data["id"] = torch.arange(g.num_nodes(item_ntype))
|
||||
|
||||
# Prepare torchtext dataset and vocabulary
|
||||
fields = {}
|
||||
examples = []
|
||||
for i in range(g.num_nodes(item_ntype)):
|
||||
example = torchtext.data.Example.fromlist([], [])
|
||||
examples.append(example)
|
||||
textset = torchtext.data.Dataset(examples, fields)
|
||||
|
||||
return PinsageDataset(g, user_ntype, item_ntype, textset)
|
||||
|
||||
|
||||
def process_data(name):
|
||||
if name == "cora":
|
||||
return dgl.data.CoraGraphDataset()
|
||||
elif name == "pubmed":
|
||||
return dgl.data.PubmedGraphDataset()
|
||||
elif name == "aifb":
|
||||
return dgl.data.AIFBDataset()
|
||||
elif name == "mutag":
|
||||
return dgl.data.MUTAGDataset()
|
||||
elif name == "bgs":
|
||||
return dgl.data.BGSDataset()
|
||||
elif name == "am":
|
||||
return dgl.data.AMDataset()
|
||||
elif name == "reddit":
|
||||
return dgl.data.RedditDataset(self_loop=True)
|
||||
elif name == "ogbn-products":
|
||||
return load_ogb_product()
|
||||
elif name == "ogbn-mag":
|
||||
return load_ogb_mag()
|
||||
elif name == "nowplaying_rs":
|
||||
return load_nowplaying_rs()
|
||||
else:
|
||||
raise ValueError("Invalid dataset name:", name)
|
||||
|
||||
|
||||
def get_bench_device():
|
||||
device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
|
||||
if device.lower() == "gpu":
|
||||
return "cuda:0"
|
||||
else:
|
||||
return device
|
||||
|
||||
|
||||
def setup_track_time(*args, **kwargs):
|
||||
# fix random seed
|
||||
np.random.seed(42)
|
||||
torch.random.manual_seed(42)
|
||||
|
||||
|
||||
def setup_track_acc(*args, **kwargs):
|
||||
# fix random seed
|
||||
np.random.seed(42)
|
||||
torch.random.manual_seed(42)
|
||||
|
||||
|
||||
def setup_track_flops(*args, **kwargs):
|
||||
# fix random seed
|
||||
np.random.seed(42)
|
||||
torch.random.manual_seed(42)
|
||||
|
||||
|
||||
TRACK_UNITS = {
|
||||
"time": "s",
|
||||
"acc": "%",
|
||||
"flops": "GFLOPS",
|
||||
}
|
||||
|
||||
TRACK_SETUP = {
|
||||
"time": setup_track_time,
|
||||
"acc": setup_track_acc,
|
||||
"flops": setup_track_flops,
|
||||
}
|
||||
|
||||
|
||||
def parametrize(param_name, params):
|
||||
"""Decorator for benchmarking over a set of parameters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
param_name : str
|
||||
Parameter name. Must be one of the arguments of the decorated function.
|
||||
params : list[any]
|
||||
List of values to benchmark for the given parameter name. Recommend
|
||||
to use Python's native object type (e.g., int, str, list[int]) because
|
||||
ASV will display them on the plot.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Benchmark function `foo` when argument `x` is equal to 10 or 20.
|
||||
|
||||
.. code::
|
||||
@benchmark('time')
|
||||
@parametrize('x', [10, 20]):
|
||||
def foo(x):
|
||||
pass
|
||||
|
||||
Benchmark function with multiple parametrizations. It will run the function
|
||||
with all possible combinations. The example below generates 6 benchmarks.
|
||||
|
||||
.. code::
|
||||
@benchmark('time')
|
||||
@parametrize('x', [10, 20]):
|
||||
@parametrize('y', [-1, -2, -3]):
|
||||
def foo(x, y):
|
||||
pass
|
||||
|
||||
When using multiple parametrizations, it can have arbitrary order. The example
|
||||
below is the same as the above one.
|
||||
|
||||
.. code::
|
||||
@benchmark('time')
|
||||
@parametrize('y', [-1, -2, -3]):
|
||||
@parametrize('x', [10, 20]):
|
||||
def foo(x, y):
|
||||
pass
|
||||
"""
|
||||
|
||||
def _wrapper(func):
|
||||
sig_params = inspect.signature(func).parameters.keys()
|
||||
num_params = len(sig_params)
|
||||
if getattr(func, "params", None) is None:
|
||||
func.params = [None] * num_params
|
||||
if getattr(func, "param_names", None) is None:
|
||||
func.param_names = [None] * num_params
|
||||
found_param = False
|
||||
for i, sig_param in enumerate(sig_params):
|
||||
if sig_param == param_name:
|
||||
func.params[i] = params
|
||||
func.param_names[i] = param_name
|
||||
found_param = True
|
||||
break
|
||||
if not found_param:
|
||||
raise ValueError("Invalid parameter name:", param_name)
|
||||
return func
|
||||
|
||||
return _wrapper
|
||||
|
||||
|
||||
def noop_decorator(param_name, params):
|
||||
"""noop decorator"""
|
||||
|
||||
def _wrapper(func):
|
||||
return func
|
||||
|
||||
return _wrapper
|
||||
|
||||
|
||||
class TestFilter:
|
||||
def __init__(self):
|
||||
self.conf = None
|
||||
if "DGL_REG_CONF" in os.environ:
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
path = os.path.join(
|
||||
current_dir, "../../", os.environ["DGL_REG_CONF"]
|
||||
)
|
||||
with open(path, "r") as f:
|
||||
self.conf = json.load(f)
|
||||
if "INSTANCE_TYPE" in os.environ:
|
||||
instance_type = os.environ["INSTANCE_TYPE"]
|
||||
else:
|
||||
raise Exception(
|
||||
"Must set both DGL_REG_CONF and INSTANCE_TYPE as env"
|
||||
)
|
||||
self.enabled_tests = self.conf[instance_type]["tests"]
|
||||
else:
|
||||
import logging
|
||||
|
||||
logging.warning("No regression test conf file specified")
|
||||
|
||||
def check(self, func):
|
||||
funcfullname = inspect.getmodule(func).__name__ + "." + func.__name__
|
||||
if self.conf is None:
|
||||
return True
|
||||
else:
|
||||
for enabled_testname in self.enabled_tests:
|
||||
if enabled_testname in funcfullname:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
filter = TestFilter()
|
||||
|
||||
|
||||
device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
|
||||
|
||||
if device == "cpu":
|
||||
parametrize_cpu = parametrize
|
||||
parametrize_gpu = noop_decorator
|
||||
elif device == "gpu":
|
||||
parametrize_cpu = noop_decorator
|
||||
parametrize_gpu = parametrize
|
||||
else:
|
||||
raise Exception(
|
||||
"Unknown device. Must be one of ['cpu', 'gpu'], but got {}".format(
|
||||
device
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def skip_if_gpu():
|
||||
"""skip if DGL_BENCH_DEVICE is gpu"""
|
||||
device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
|
||||
|
||||
def _wrapper(func):
|
||||
if device == "gpu":
|
||||
# skip if not enabled
|
||||
func.benchmark_name = "skip_" + func.__name__
|
||||
return func
|
||||
|
||||
return _wrapper
|
||||
|
||||
|
||||
def _cuda_device_count(q):
|
||||
import torch
|
||||
|
||||
q.put(torch.cuda.device_count())
|
||||
|
||||
|
||||
def get_num_gpu():
|
||||
import multiprocessing as mp
|
||||
|
||||
q = mp.Queue()
|
||||
p = mp.Process(target=_cuda_device_count, args=(q,))
|
||||
p.start()
|
||||
p.join()
|
||||
return q.get(block=False)
|
||||
|
||||
|
||||
GPU_COUNT = get_num_gpu()
|
||||
|
||||
|
||||
def skip_if_not_4gpu():
|
||||
"""skip if DGL_BENCH_DEVICE is gpu"""
|
||||
|
||||
def _wrapper(func):
|
||||
if GPU_COUNT < 4:
|
||||
# skip if not enabled
|
||||
print("Skip {}".format(func.__name__))
|
||||
func.benchmark_name = "skip_" + func.__name__
|
||||
return func
|
||||
|
||||
return _wrapper
|
||||
|
||||
|
||||
def benchmark(track_type, timeout=60):
|
||||
"""Decorator for indicating the benchmark type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
track_type : str
|
||||
Type. Must be either:
|
||||
|
||||
- 'time' : For timing. Unit: second.
|
||||
- 'acc' : For accuracy. Unit: percentage, value between 0 and 100.
|
||||
- 'flops' : Unit: GFlops, number of floating point operations per second.
|
||||
timeout : int
|
||||
Timeout threshold in second.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code::
|
||||
@benchmark('time')
|
||||
def foo():
|
||||
pass
|
||||
"""
|
||||
assert track_type in ["time", "acc", "flops"]
|
||||
|
||||
def _wrapper(func):
|
||||
func.unit = TRACK_UNITS[track_type]
|
||||
func.setup = TRACK_SETUP[track_type]
|
||||
func.timeout = timeout
|
||||
if not filter.check(func):
|
||||
# skip if not enabled
|
||||
func.benchmark_name = "skip_" + func.__name__
|
||||
return func
|
||||
|
||||
return _wrapper
|
||||
|
||||
|
||||
#####################################
|
||||
# Timer
|
||||
#####################################
|
||||
|
||||
|
||||
class Timer:
|
||||
def __init__(self, device=None):
|
||||
self.timer = default_timer
|
||||
if device is None:
|
||||
self.device = get_bench_device()
|
||||
else:
|
||||
self.device = device
|
||||
|
||||
def __enter__(self):
|
||||
if self.device == "cuda:0":
|
||||
self.start_event = torch.cuda.Event(enable_timing=True)
|
||||
self.end_event = torch.cuda.Event(enable_timing=True)
|
||||
self.start_event.record()
|
||||
else:
|
||||
self.tic = self.timer()
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if self.device == "cuda:0":
|
||||
self.end_event.record()
|
||||
torch.cuda.synchronize() # Wait for the events to be recorded!
|
||||
self.elapsed_secs = (
|
||||
self.start_event.elapsed_time(self.end_event) / 1e3
|
||||
)
|
||||
else:
|
||||
self.elapsed_secs = self.timer() - self.tic
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
DEVICE=$1
|
||||
ROOT=/asv/dgl
|
||||
|
||||
. /opt/conda/etc/profile.d/conda.sh
|
||||
|
||||
conda activate base
|
||||
pip install --upgrade pip
|
||||
# Newer asv version like 0.5.1 has different result format,
|
||||
# so we fix the version here. Or `generate_excel.py` has to be changed.
|
||||
pip install asv==0.4.2
|
||||
pip uninstall -y dgl
|
||||
|
||||
export DGL_BENCH_DEVICE=$DEVICE
|
||||
echo "DGL_BENCH_DEVICE=$DGL_BENCH_DEVICE"
|
||||
pushd $ROOT/benchmarks
|
||||
cat asv.conf.json
|
||||
asv machine --yes
|
||||
# If --launch-method is specified as 'spawn', multigpu tests will crash with
|
||||
# "No module named 'benchmarks' is found".
|
||||
asv run -e -v
|
||||
asv publish
|
||||
popd
|
||||
@@ -0,0 +1,32 @@
|
||||
Regression Test Suite
|
||||
========================
|
||||
|
||||
### Spec of task.json
|
||||
```json
|
||||
# Note the test will be run if the name specified below is a substring of the full test name.
|
||||
# The fullname of "benchmarks/model_acc/bench_sage_ns.track_acc" will be "model_acc.bench_sage_ns.track_acc". Test will be run if it contains any keyword.
|
||||
# For example, "model_acc" will run all the tests under "model_acc" folder
|
||||
# "bench_sage" will run both "bench_sage" and "bench_sage_ns"
|
||||
# "bench_sage." will only run "bench_sage"
|
||||
# "ns" will run any tests name contains "ms"
|
||||
# "" will run all tests
|
||||
{
|
||||
"c5.9xlarge": { # The instance type to run the test
|
||||
"tests": [
|
||||
"bench_sage" # The test to be run on this instance
|
||||
],
|
||||
"env": {
|
||||
"DEVICE": "cpu" # The environment variable passed to publish.sh
|
||||
}
|
||||
},
|
||||
"g4dn.2xlarge": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Environment variable
|
||||
- `MOUNT_PATH` specify the directory in the host to be mapped into docker, if exists will map the `MOUNT_PATH`(in host) to `/tmp/dataset`(in docker)
|
||||
- `INSTANCE_TYPE` specify the current instance type
|
||||
- `DGL_REG_CONF` specify the path to `task.json`, which is relative to the repo root. If specified, must specify `INSTANCE_TYPE` also
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Default building only with cpu
|
||||
DEVICE=${DGL_BENCH_DEVICE:-cpu}
|
||||
|
||||
pip install -r /asv/torch_gpu_pip.txt
|
||||
|
||||
# build
|
||||
# 'CUDA_TOOLKIT_ROOT_DIR' is always required for sparse build as torch1.13.1+cu116 is installed.
|
||||
CMAKE_VARS="-DUSE_OPENMP=ON -DBUILD_TORCH=ON -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda"
|
||||
if [[ $DEVICE == "gpu" ]]; then
|
||||
CMAKE_VARS="-DUSE_CUDA=ON $CMAKE_VARS"
|
||||
fi
|
||||
mkdir -p build
|
||||
pushd build
|
||||
cmake $CMAKE_VARS ..
|
||||
make -j8
|
||||
popd
|
||||
@@ -0,0 +1,28 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
result_dir = Path(__file__).parent / ".." / Path("results/")
|
||||
for per_machine_dir in result_dir.iterdir():
|
||||
if per_machine_dir.is_dir():
|
||||
try:
|
||||
machine_json = json.loads(
|
||||
(per_machine_dir / "machine.json").read_text()
|
||||
)
|
||||
ram = machine_json["ram"]
|
||||
for f in per_machine_dir.glob("*.json"):
|
||||
if f.stem != "machine":
|
||||
result = json.loads(f.read_text())
|
||||
result_ram = result["params"]["ram"]
|
||||
if result_ram != ram:
|
||||
result["params"]["ram"] = ram
|
||||
print(f"Fix ram in {f}")
|
||||
f.write_text(json.dumps(result))
|
||||
else:
|
||||
print(f"Skip {f}")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
import json
|
||||
from itertools import product
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def get_branch_name_from_hash(hash):
|
||||
import subprocess
|
||||
|
||||
process = subprocess.Popen(
|
||||
["git", "name-rev", "--name-only", hash],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = process.communicate()
|
||||
if len(stderr) > 0:
|
||||
return hash[:10]
|
||||
else:
|
||||
return stdout.decode("utf-8").strip("\n")
|
||||
|
||||
|
||||
def main():
|
||||
results_path = Path("../results")
|
||||
results_path.is_dir()
|
||||
machines = [f for f in results_path.glob("*") if f.is_dir()]
|
||||
output_results_dict = {}
|
||||
for machine in machines:
|
||||
per_machine_result = {}
|
||||
commit_results_json_paths = [
|
||||
f for f in machine.glob("*") if f.name != "machine.json"
|
||||
]
|
||||
for commit in commit_results_json_paths:
|
||||
with commit.open() as f:
|
||||
commit_result = json.load(f)
|
||||
commit_hash = commit_result["commit_hash"]
|
||||
per_commit_result = {}
|
||||
for test_name, result in commit_result["results"].items():
|
||||
per_commit_result[test_name] = []
|
||||
if result["result"] is None:
|
||||
for test_args in product(*result["params"]):
|
||||
per_commit_result[test_name].append(
|
||||
{"params": ", ".join(test_args), "result": None}
|
||||
)
|
||||
else:
|
||||
for test_args, performance_number in zip(
|
||||
product(*result["params"]), result["result"]
|
||||
):
|
||||
per_commit_result[test_name].append(
|
||||
{
|
||||
"params": ", ".join(test_args),
|
||||
"result": performance_number,
|
||||
}
|
||||
)
|
||||
per_machine_result[commit_hash] = per_commit_result
|
||||
output_results_dict[machine.name] = per_machine_result
|
||||
return output_results_dict
|
||||
|
||||
|
||||
def dict_to_csv(output_results_dict):
|
||||
with open("../results/benchmarks.json") as f:
|
||||
benchmark_conf = json.load(f)
|
||||
unit_dict = {}
|
||||
for k, v in benchmark_conf.items():
|
||||
if k != "version":
|
||||
unit_dict[k] = v["unit"]
|
||||
result_list = []
|
||||
for machine, per_machine_result in output_results_dict.items():
|
||||
for commit, test_cases in per_machine_result.items():
|
||||
branch_name = get_branch_name_from_hash(commit)
|
||||
result_column_name = "number_{}".format(branch_name)
|
||||
# per_commit_result_list = []
|
||||
for test_case_name, results in test_cases.items():
|
||||
for result in results:
|
||||
result_list.append(
|
||||
{
|
||||
"test_name": test_case_name,
|
||||
"params": result["params"],
|
||||
"unit": unit_dict[test_case_name],
|
||||
"number": result["result"],
|
||||
"commit": branch_name,
|
||||
"machine": machine,
|
||||
}
|
||||
)
|
||||
df = pd.DataFrame(result_list)
|
||||
return df
|
||||
|
||||
|
||||
def side_by_side_view(df):
|
||||
commits = df["commit"].unique().tolist()
|
||||
full_df = df.loc[df["commit"] == commits[0]]
|
||||
for commit in commits[1:]:
|
||||
per_commit_df = df.loc[df["commit"] == commit]
|
||||
full_df: pd.DataFrame = full_df.merge(
|
||||
per_commit_df,
|
||||
on=["test_name", "params", "machine", "unit"],
|
||||
how="outer",
|
||||
suffixes=(
|
||||
"_{}".format(full_df.iloc[0]["commit"]),
|
||||
"_{}".format(per_commit_df.iloc[0]["commit"]),
|
||||
),
|
||||
)
|
||||
full_df = full_df.loc[:, ~full_df.columns.str.startswith("commit")]
|
||||
return full_df
|
||||
|
||||
|
||||
output_results_dict = main()
|
||||
df = dict_to_csv(output_results_dict)
|
||||
sbs_df = side_by_side_view(df)
|
||||
sbs_df.to_csv("result.csv")
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# install
|
||||
pushd python
|
||||
rm -rf build *.egg-info dist
|
||||
pip uninstall -y dgl
|
||||
python3 setup.py install
|
||||
popd
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# The script launches a docker container to run ASV benchmarks. We use the same docker
|
||||
# image as our CI (i.e., dgllib/dgl-ci-gpu:conda). It performs the following steps:
|
||||
#
|
||||
# 1. Start a docker container of the given machine name. The machine name will be
|
||||
# displayed on the generated website.
|
||||
# 2. Copy `.git` into the container. It allows ASV to determine the repository information
|
||||
# such as commit hash, branches, etc.
|
||||
# 3. Copy this folder into the container including the ASV configuration file `asv.conf.json`.
|
||||
# This means any changes to the files in this folder do not
|
||||
# require a git commit. By contrast, to correctly benchmark your changes to the core
|
||||
# library (e.g., "python/dgl"), you must call git commit first.
|
||||
# 4. It then calls the `run.sh` script inside the container. It will invoke `asv run`.
|
||||
# You can change the command such as specifying the benchmarks to run or adding some flags.
|
||||
# 5. After benchmarking, it copies the generated `results` and `html` folders back to
|
||||
# the host machine.
|
||||
#
|
||||
|
||||
if [ $# -eq 2 ]; then
|
||||
MACHINE=$1
|
||||
DEVICE=$2
|
||||
else
|
||||
echo "publish.sh <machine_name> <device>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WS_ROOT=/asv/dgl
|
||||
docker pull public.ecr.aws/s1o7b3d9/benchmark_test:cu116_v230110
|
||||
if [ -z "$DGL_REG_CONF" ]; then
|
||||
DOCKER_ENV_OPT="$DOCKER_ENV_OPT"
|
||||
else
|
||||
DOCKER_ENV_OPT=" -e DGL_REG_CONF=$DGL_REG_CONF $DOCKER_ENV_OPT"
|
||||
fi
|
||||
|
||||
if [ -z "$INSTANCE_TYPE" ]; then
|
||||
DOCKER_ENV_OPT="$DOCKER_ENV_OPT"
|
||||
else
|
||||
DOCKER_ENV_OPT=" -e INSTANCE_TYPE=$INSTANCE_TYPE $DOCKER_ENV_OPT"
|
||||
fi
|
||||
|
||||
if [ -z "$MOUNT_PATH" ]; then
|
||||
DOCKER_MOUNT_OPT=""
|
||||
else
|
||||
DOCKER_MOUNT_OPT="-v ${MOUNT_PATH}:/tmp/dataset -v ${MOUNT_PATH}/dgl_home/:/root/.dgl/"
|
||||
fi
|
||||
|
||||
echo $HOME
|
||||
echo "Mount Point: ${DOCKER_MOUNT_OPT}"
|
||||
echo "Env opt: ${DOCKER_ENV_OPT}"
|
||||
echo "DEVICE: ${DEVICE}"
|
||||
|
||||
if [[ $DEVICE == "cpu" ]]; then
|
||||
docker run --name dgl-reg \
|
||||
--rm \
|
||||
$DOCKER_MOUNT_OPT \
|
||||
$DOCKER_ENV_OPT \
|
||||
--shm-size="16g" \
|
||||
--hostname=$MACHINE -dit public.ecr.aws/s1o7b3d9/benchmark_test:cu116_v230110 /bin/bash
|
||||
else
|
||||
docker run --name dgl-reg \
|
||||
--rm --gpus all \
|
||||
$DOCKER_MOUNT_OPT \
|
||||
$DOCKER_ENV_OPT \
|
||||
--shm-size="16g" \
|
||||
--hostname=$MACHINE -dit public.ecr.aws/s1o7b3d9/benchmark_test:cu116_v230110 /bin/bash
|
||||
fi
|
||||
|
||||
pwd
|
||||
|
||||
docker exec dgl-reg mkdir -p $WS_ROOT
|
||||
docker cp ../../.git dgl-reg:$WS_ROOT
|
||||
docker cp ../ dgl-reg:$WS_ROOT/benchmarks/
|
||||
docker cp torch_gpu_pip.txt dgl-reg:/asv
|
||||
docker exec $DOCKER_ENV_OPT dgl-reg bash $WS_ROOT/benchmarks/run.sh $DEVICE
|
||||
docker cp dgl-reg:$WS_ROOT/benchmarks/results ../
|
||||
docker cp dgl-reg:$WS_ROOT/benchmarks/html ../
|
||||
docker stop dgl-reg
|
||||
@@ -0,0 +1,82 @@
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def json_minify(string, strip_space=True):
|
||||
"""
|
||||
Based on JSON.minify.js:
|
||||
https://github.com/getify/JSON.minify
|
||||
Contributers:
|
||||
- Pradyun S. Gedam (conditions and variable names changed)
|
||||
"""
|
||||
tokenizer = re.compile(r'"|(/\*)|(\*/)|(//)|\n|\r')
|
||||
in_string = False
|
||||
in_multi = False
|
||||
in_single = False
|
||||
|
||||
new_str = []
|
||||
index = 0
|
||||
|
||||
for match in re.finditer(tokenizer, string):
|
||||
if not (in_multi or in_single):
|
||||
tmp = string[index : match.start()]
|
||||
if not in_string and strip_space:
|
||||
# replace white space as defined in standard
|
||||
tmp = re.sub("[ \t\n\r]+", "", tmp)
|
||||
new_str.append(tmp)
|
||||
|
||||
index = match.end()
|
||||
val = match.group()
|
||||
|
||||
if val == '"' and not (in_multi or in_single):
|
||||
escaped = re.search(r"(\\)*$", string[: match.start()])
|
||||
|
||||
# start of string or unescaped quote character to end string
|
||||
if not in_string or (
|
||||
escaped is None or len(escaped.group()) % 2 == 0
|
||||
):
|
||||
in_string = not in_string
|
||||
index -= 1 # include " character in next catch
|
||||
elif not (in_string or in_multi or in_single):
|
||||
if val == "/*":
|
||||
in_multi = True
|
||||
elif val == "//":
|
||||
in_single = True
|
||||
elif val == "*/" and in_multi and not (in_string or in_single):
|
||||
in_multi = False
|
||||
elif val in "\r\n" and not (in_multi or in_string) and in_single:
|
||||
in_single = False
|
||||
elif not (
|
||||
(in_multi or in_single) or (val in " \r\n\t" and strip_space)
|
||||
):
|
||||
new_str.append(val)
|
||||
|
||||
new_str.append(string[index:])
|
||||
content = "".join(new_str)
|
||||
content = content.replace(",]", "]")
|
||||
content = content.replace(",}", "}")
|
||||
return content
|
||||
|
||||
|
||||
def add_prefix(branch_name):
|
||||
if "/" not in branch_name:
|
||||
return "origin/" + branch_name
|
||||
else:
|
||||
return branch_name
|
||||
|
||||
|
||||
def change_branch(branch_str: str):
|
||||
branches = [add_prefix(b) for b in branch_str.split(",")]
|
||||
with open("../asv.conf.json", "r") as f:
|
||||
ss = f.read()
|
||||
config_json = json.loads(json_minify(ss))
|
||||
config_json["branches"] = branches
|
||||
with open("../asv.conf.json", "w") as f:
|
||||
json.dump(config_json, f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "BRANCH_STR" in os.environ:
|
||||
change_branch(os.environ["BRANCH_STR"])
|
||||
@@ -0,0 +1,19 @@
|
||||
--find-links https://download.pytorch.org/whl/torch_stable.html
|
||||
torch==1.13.1+cu116
|
||||
torchvision==0.14.1+cu116
|
||||
torchmetrics
|
||||
pytest
|
||||
nose
|
||||
numpy
|
||||
cython
|
||||
scipy
|
||||
networkx
|
||||
matplotlib
|
||||
nltk
|
||||
requests[security]
|
||||
tqdm
|
||||
awscli
|
||||
torchtext
|
||||
pandas
|
||||
rdflib
|
||||
ogb
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"r5.16xlarge": {
|
||||
"tests": [
|
||||
"api.", "kernel.", "model_acc.", "model_speed."
|
||||
],
|
||||
"env": {
|
||||
"DEVICE": "cpu"
|
||||
}
|
||||
},
|
||||
"g4dn.2xlarge": {
|
||||
"tests": [
|
||||
"api.", "kernel.", "model_acc.", "model_speed."
|
||||
],
|
||||
"env": {
|
||||
"DEVICE": "gpu"
|
||||
}
|
||||
},
|
||||
"g4dn.12xlarge": {
|
||||
"tests": [
|
||||
"multigpu."
|
||||
],
|
||||
"env": {
|
||||
"DEVICE": "gpu"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
# CUDA Module
|
||||
if(USE_CUDA)
|
||||
find_cuda(${USE_CUDA} REQUIRED)
|
||||
else(USE_CUDA)
|
||||
return()
|
||||
endif()
|
||||
|
||||
###### Borrowed from MSHADOW project
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag("-std=c++17" SUPPORT_CXX17)
|
||||
|
||||
set(dgl_known_gpu_archs "35" "50" "60" "70" "75")
|
||||
set(dgl_cuda_arch_ptx "70")
|
||||
if (CUDA_VERSION_MAJOR GREATER_EQUAL "11")
|
||||
list(APPEND dgl_known_gpu_archs "80" "86")
|
||||
set(dgl_cuda_arch_ptx "80" "86")
|
||||
endif()
|
||||
if (CUDA_VERSION VERSION_GREATER_EQUAL "11.8")
|
||||
list(APPEND dgl_known_gpu_archs "89" "90")
|
||||
set(dgl_cuda_arch_ptx "90")
|
||||
endif()
|
||||
if (CUDA_VERSION VERSION_GREATER_EQUAL "12.0")
|
||||
list(REMOVE_ITEM dgl_known_gpu_archs "35")
|
||||
endif()
|
||||
|
||||
################################################################################################
|
||||
# A function for automatic detection of GPUs installed (if autodetection is enabled)
|
||||
# Usage:
|
||||
# dgl_detect_installed_gpus(out_variable)
|
||||
function(dgl_detect_installed_gpus out_variable)
|
||||
set(CUDA_gpu_detect_output "")
|
||||
if(NOT CUDA_gpu_detect_output)
|
||||
message(STATUS "Running GPU architecture autodetection")
|
||||
set(__cufile ${PROJECT_BINARY_DIR}/detect_cuda_archs.cu)
|
||||
|
||||
file(WRITE ${__cufile} ""
|
||||
"#include <cstdio>\n"
|
||||
"#include <iostream>\n"
|
||||
"using namespace std;\n"
|
||||
"int main()\n"
|
||||
"{\n"
|
||||
" int count = 0;\n"
|
||||
" if (cudaSuccess != cudaGetDeviceCount(&count)) { return -1; }\n"
|
||||
" if (count == 0) { cerr << \"No cuda devices detected\" << endl; return -1; }\n"
|
||||
" for (int device = 0; device < count; ++device)\n"
|
||||
" {\n"
|
||||
" cudaDeviceProp prop;\n"
|
||||
" if (cudaSuccess == cudaGetDeviceProperties(&prop, device))\n"
|
||||
" std::printf(\"%d.%d \", prop.major, prop.minor);\n"
|
||||
" }\n"
|
||||
" return 0;\n"
|
||||
"}\n")
|
||||
if(MSVC)
|
||||
#find vcvarsall.bat and run it building msvc environment
|
||||
get_filename_component(MY_COMPILER_DIR ${CMAKE_CXX_COMPILER} DIRECTORY)
|
||||
find_file(MY_VCVARSALL_BAT vcvarsall.bat "${MY_COMPILER_DIR}/.." "${MY_COMPILER_DIR}/../..")
|
||||
execute_process(COMMAND ${MY_VCVARSALL_BAT} && ${CUDA_NVCC_EXECUTABLE} -arch native --run ${__cufile}
|
||||
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/CMakeFiles/"
|
||||
RESULT_VARIABLE __nvcc_res OUTPUT_VARIABLE __nvcc_out
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
else()
|
||||
if(CUDA_LIBRARY_PATH)
|
||||
set(CUDA_LINK_LIBRARY_PATH "-L${CUDA_LIBRARY_PATH}")
|
||||
endif()
|
||||
execute_process(COMMAND ${CUDA_NVCC_EXECUTABLE} -arch native --run ${__cufile} ${CUDA_LINK_LIBRARY_PATH}
|
||||
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/CMakeFiles/"
|
||||
RESULT_VARIABLE __nvcc_res OUTPUT_VARIABLE __nvcc_out
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
endif()
|
||||
if(__nvcc_res EQUAL 0)
|
||||
# nvcc outputs text containing line breaks when building with MSVC.
|
||||
# The line below prevents CMake from inserting a variable with line
|
||||
# breaks in the cache
|
||||
message(STATUS "Found GPU arch ${__nvcc_out}")
|
||||
string(REGEX MATCH "([1-9].[0-9])" __nvcc_out "${__nvcc_out}")
|
||||
if(__nvcc_out VERSION_LESS "3.5")
|
||||
# drop support for cc < 3.5 and build for all known archs.
|
||||
message(WARNING "GPU arch less than 3.5 is not supported.")
|
||||
else()
|
||||
set(CUDA_gpu_detect_output ${__nvcc_out} CACHE INTERNAL "Returned GPU architetures from mshadow_detect_gpus tool" FORCE)
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "Running GPU detection script with nvcc failed: ${__nvcc_out}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CUDA_gpu_detect_output)
|
||||
message(WARNING "Automatic GPU detection failed. Building for all known architectures (${dgl_known_gpu_archs}).")
|
||||
set(${out_variable} ${dgl_known_gpu_archs} PARENT_SCOPE)
|
||||
else()
|
||||
set(${out_variable} ${CUDA_gpu_detect_output} PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
################################################################################################
|
||||
# Function for selecting GPU arch flags for nvcc based on CUDA_ARCH_NAME
|
||||
# Usage:
|
||||
# dgl_select_nvcc_arch_flags(out_variable)
|
||||
function(dgl_select_nvcc_arch_flags out_variable)
|
||||
# List of arch names. Turing and Ada don't have a new major version, so they are not added to default build.
|
||||
set(__archs_names "Kepler" "Maxwell" "Pascal" "Volta" "Turing" "Ampere" "Ada" "Hopper" "All" "Manual")
|
||||
if (NOT CUDA_VERSION VERSION_LESS "12.0")
|
||||
list(REMOVE_ITEM __archs_names "Kepler")
|
||||
endif()
|
||||
set(__archs_name_default "All")
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
list(APPEND __archs_names "Auto")
|
||||
set(__archs_name_default "Auto")
|
||||
endif()
|
||||
|
||||
# set CUDA_ARCH_NAME strings (so it will be seen as dropbox in CMake-Gui)
|
||||
set(CUDA_ARCH_NAME ${__archs_name_default} CACHE STRING "Select target NVIDIA GPU achitecture.")
|
||||
set_property( CACHE CUDA_ARCH_NAME PROPERTY STRINGS "" ${__archs_names} )
|
||||
mark_as_advanced(CUDA_ARCH_NAME)
|
||||
|
||||
# verify CUDA_ARCH_NAME value
|
||||
if(NOT ";${__archs_names};" MATCHES ";${CUDA_ARCH_NAME};")
|
||||
string(REPLACE ";" ", " __archs_names "${__archs_names}")
|
||||
message(FATAL_ERROR "Only ${__archs_names} architeture names are supported.")
|
||||
endif()
|
||||
|
||||
if(${CUDA_ARCH_NAME} STREQUAL "Manual")
|
||||
set(CUDA_ARCH_BIN ${dgl_known_gpu_archs} CACHE STRING "Specify 'real' GPU architectures to build binaries for, BIN(PTX) format is supported")
|
||||
set(CUDA_ARCH_PTX ${dgl_cuda_arch_ptx} CACHE STRING "Specify 'virtual' PTX architectures to build PTX intermediate code for")
|
||||
mark_as_advanced(CUDA_ARCH_BIN CUDA_ARCH_PTX)
|
||||
else()
|
||||
unset(CUDA_ARCH_BIN CACHE)
|
||||
unset(CUDA_ARCH_PTX CACHE)
|
||||
endif()
|
||||
|
||||
if(${CUDA_ARCH_NAME} STREQUAL "Kepler")
|
||||
set(__cuda_arch_bin "35")
|
||||
set(__cuda_arch_ptx "35")
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "Maxwell")
|
||||
set(__cuda_arch_bin "50")
|
||||
set(__cuda_arch_ptx "50")
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "Pascal")
|
||||
set(__cuda_arch_bin "60")
|
||||
set(__cuda_arch_ptx "60")
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "Volta")
|
||||
set(__cuda_arch_bin "70")
|
||||
set(__cuda_arch_ptx "70")
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "Turing")
|
||||
set(__cuda_arch_bin "75")
|
||||
set(__cuda_arch_ptx "75")
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "Ampere")
|
||||
set(__cuda_arch_bin "80")
|
||||
set(__cuda_arch_ptx "80")
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "Ada")
|
||||
set(__cuda_arch_bin "89")
|
||||
set(__cuda_arch_ptx "89")
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "Hopper")
|
||||
set(__cuda_arch_bin "90")
|
||||
set(__cuda_arch_ptx "90")
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "All")
|
||||
set(__cuda_arch_bin ${dgl_known_gpu_archs})
|
||||
set(__cuda_arch_ptx ${dgl_cuda_arch_ptx})
|
||||
elseif(${CUDA_ARCH_NAME} STREQUAL "Auto")
|
||||
dgl_detect_installed_gpus(__cuda_arch_bin)
|
||||
# if detect successes, __cuda_arch_ptx = __cuda_arch_bin
|
||||
# if detect fails, __cuda_arch_ptx is the latest arch in __cuda_arch_bin
|
||||
list(GET __cuda_arch_bin -1 __cuda_arch_ptx)
|
||||
else() # (${CUDA_ARCH_NAME} STREQUAL "Manual")
|
||||
set(__cuda_arch_bin ${CUDA_ARCH_BIN})
|
||||
set(__cuda_arch_ptx ${CUDA_ARCH_PTX})
|
||||
endif()
|
||||
|
||||
# remove dots and convert to lists
|
||||
string(REGEX REPLACE "\\." "" __cuda_arch_bin "${__cuda_arch_bin}")
|
||||
string(REGEX REPLACE "\\." "" __cuda_arch_ptx "${__cuda_arch_ptx}")
|
||||
string(REGEX MATCHALL "[0-9()]+" __cuda_arch_bin "${__cuda_arch_bin}")
|
||||
string(REGEX MATCHALL "[0-9]+" __cuda_arch_ptx "${__cuda_arch_ptx}")
|
||||
mshadow_list_unique(__cuda_arch_bin __cuda_arch_ptx)
|
||||
|
||||
set(__nvcc_flags "--expt-relaxed-constexpr")
|
||||
set(__nvcc_archs_readable "")
|
||||
set(__archs "")
|
||||
|
||||
# Tell NVCC to add binaries for the specified GPUs
|
||||
foreach(__arch ${__cuda_arch_bin})
|
||||
if(__arch MATCHES "([0-9]+)\\(([0-9]+)\\)")
|
||||
# User explicitly specified PTX for the concrete BIN
|
||||
list(APPEND __nvcc_flags -gencode arch=compute_${CMAKE_MATCH_2},code=sm_${CMAKE_MATCH_1})
|
||||
list(APPEND __nvcc_archs_readable sm_${CMAKE_MATCH_1})
|
||||
list(APPEND __archs ${CMAKE_MATCH_1})
|
||||
else()
|
||||
# User didn't explicitly specify PTX for the concrete BIN, we assume PTX=BIN
|
||||
list(APPEND __nvcc_flags -gencode arch=compute_${__arch},code=sm_${__arch})
|
||||
list(APPEND __nvcc_archs_readable sm_${__arch})
|
||||
list(APPEND __archs ${__arch})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Tell NVCC to add PTX intermediate code for the specified architectures
|
||||
foreach(__arch ${__cuda_arch_ptx})
|
||||
list(APPEND __nvcc_flags -gencode arch=compute_${__arch},code=compute_${__arch})
|
||||
list(APPEND __nvcc_archs_readable compute_${__arch})
|
||||
endforeach()
|
||||
|
||||
string(REPLACE ";" " " __nvcc_archs_readable "${__nvcc_archs_readable}")
|
||||
set(${out_variable} ${__nvcc_flags} PARENT_SCOPE)
|
||||
set(${out_variable}_readable ${__nvcc_archs_readable} PARENT_SCOPE)
|
||||
set(CUDA_ARCHITECTURES ${__archs} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
################################################################################################
|
||||
# Config cuda compilation and append CUDA libraries to linker_libs
|
||||
# Usage:
|
||||
# dgl_config_cuda(linker_libs)
|
||||
macro(dgl_config_cuda linker_libs)
|
||||
if(NOT CUDA_FOUND)
|
||||
message(FATAL_ERROR "Cannot find CUDA.")
|
||||
endif()
|
||||
# always set the includedir when cuda is available
|
||||
# avoid global retrigger of cmake
|
||||
include_directories(${CUDA_INCLUDE_DIRS})
|
||||
|
||||
add_definitions(-DDGL_USE_CUDA)
|
||||
|
||||
# NVCC flags
|
||||
# Manually set everything
|
||||
set(CUDA_PROPAGATE_HOST_FLAGS OFF)
|
||||
|
||||
# 0. Add host flags
|
||||
message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
|
||||
string(REGEX REPLACE "[ \t\n\r]" "," CXX_HOST_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
if(MSVC AND NOT USE_MSVC_MT)
|
||||
string(CONCAT CXX_HOST_FLAGS ${CXX_HOST_FLAGS} ",/MD")
|
||||
endif()
|
||||
list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "${CXX_HOST_FLAGS}")
|
||||
if(USE_OPENMP)
|
||||
# Needed by CUDA disjoint union source file.
|
||||
list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "${OpenMP_CXX_FLAGS}")
|
||||
endif(USE_OPENMP)
|
||||
|
||||
# 1. Add arch flags
|
||||
dgl_select_nvcc_arch_flags(NVCC_FLAGS_ARCH)
|
||||
list(APPEND CUDA_NVCC_FLAGS ${NVCC_FLAGS_ARCH})
|
||||
|
||||
# 2. flags in third_party/moderngpu
|
||||
list(APPEND CUDA_NVCC_FLAGS "--expt-extended-lambda;-Wno-deprecated-declarations;-std=c++17")
|
||||
|
||||
message(STATUS "CUDA_NVCC_FLAGS: ${CUDA_NVCC_FLAGS}")
|
||||
|
||||
list(APPEND ${linker_libs}
|
||||
${CUDA_CUDART_LIBRARY}
|
||||
${CUDA_CUBLAS_LIBRARIES}
|
||||
${CUDA_cusparse_LIBRARY})
|
||||
endmacro()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user