From 6760f29bd0b1a4fb496e246a269f55da27cd0f1c Mon Sep 17 00:00:00 2001 From: jason-on-salt-a40 Date: Thu, 21 Mar 2024 11:02:20 -0700 Subject: [PATCH] init --- .gitignore | 25 + LICENSE-CODE | 437 +++++ LICENSE-MODEL | 42 + README.md | 69 + config.py | 86 + data/__init__.py | 0 data/giga_preprocessing/encodec_encode.py | 160 ++ data/gigaspeech.py | 158 ++ data/tokenizer.py | 149 ++ demo/84_121550_000074_000000.wav | Bin 0 -> 507578 bytes demo/temp/84_121550_000074_000000.txt | 1 + .../84_121550_000074_000000.csv | 109 ++ edit_utils.py | 49 + inference_speech_editing.ipynb | 209 +++ inference_speech_editing_scale.py | 226 +++ inference_tts.ipynb | 182 +++ inference_tts_scale.py | 190 +++ main.py | 45 + models/codebooks_patterns.py | 538 +++++++ models/modules/__init__.py | 0 models/modules/activation.py | 653 ++++++++ models/modules/embedding.py | 98 ++ models/modules/sampling.py | 63 + models/modules/scaling.py | 1406 +++++++++++++++++ models/modules/transformer.py | 698 ++++++++ models/modules/utils.py | 37 + models/voicecraft.py | 1402 ++++++++++++++++ steps/__init__.py | 0 steps/optim.py | 1123 +++++++++++++ steps/trainer.py | 467 ++++++ steps/trainer_utils.py | 628 ++++++++ z_scripts/e830M.sh | 71 + 32 files changed, 9321 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE-CODE create mode 100644 LICENSE-MODEL create mode 100644 README.md create mode 100644 config.py create mode 100644 data/__init__.py create mode 100644 data/giga_preprocessing/encodec_encode.py create mode 100644 data/gigaspeech.py create mode 100644 data/tokenizer.py create mode 100644 demo/84_121550_000074_000000.wav create mode 100644 demo/temp/84_121550_000074_000000.txt create mode 100644 demo/temp/mfa_alignments/84_121550_000074_000000.csv create mode 100644 edit_utils.py create mode 100644 inference_speech_editing.ipynb create mode 100644 inference_speech_editing_scale.py create mode 100644 inference_tts.ipynb create mode 100644 inference_tts_scale.py create mode 100644 main.py create mode 100644 models/codebooks_patterns.py create mode 100644 models/modules/__init__.py create mode 100644 models/modules/activation.py create mode 100644 models/modules/embedding.py create mode 100644 models/modules/sampling.py create mode 100644 models/modules/scaling.py create mode 100644 models/modules/transformer.py create mode 100644 models/modules/utils.py create mode 100644 models/voicecraft.py create mode 100644 steps/__init__.py create mode 100644 steps/optim.py create mode 100644 steps/trainer.py create mode 100644 steps/trainer_utils.py create mode 100644 z_scripts/e830M.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9135f6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +__pycache__/ +*.py[cod] +*$py.class +*.egg-info +.pytest_cache +.ipynb_checkpoints + +thumbs.db +.DS_Store +.idea +*.log +*.pdf +*.mkv +*.mp4 +*.png +*.wav +*.mp3 + +*durip* +*rtx* +*l40* +*a40* + +!/demo/ +!/demo/* \ No newline at end of file diff --git a/LICENSE-CODE b/LICENSE-CODE new file mode 100644 index 0000000..cbe5ad1 --- /dev/null +++ b/LICENSE-CODE @@ -0,0 +1,437 @@ +Attribution-NonCommercial-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-ShareAlike 4.0 International Public License +("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-NC-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution, NonCommercial, and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + l. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + m. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + n. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-NC-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSE-MODEL b/LICENSE-MODEL new file mode 100644 index 0000000..d02930f --- /dev/null +++ b/LICENSE-MODEL @@ -0,0 +1,42 @@ +Coqui Public Model License 1.0.0 +https://coqui.ai/cpml.txt + +This license allows only non-commercial use of a machine learning model and its outputs. + +Acceptance +In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses. + +Licenses +The licensor grants you a copyright license to do everything you might do with the model that would otherwise infringe the licensor's copyright in it, for any non-commercial purpose. The licensor grants you a patent license that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the model in the form provided by the licensor, for any non-commercial purpose. + +Non-commercial Purpose +Non-commercial purposes include any of the following uses of the model or its output, but only so far as you do not receive any direct or indirect payment arising from the use of the model or its output. + +Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance. +Use by commercial or for-profit entities for testing, evaluation, or non-commercial research and development. Use of the model to train other models for commercial use is not a non-commercial purpose. +Use by any charitable organization for charitable purposes, or for testing or evaluation. Use for revenue-generating activity, including projects directly funded by government grants, is not a non-commercial purpose. +Notices +You must ensure that anyone who gets a copy of any part of the model, or any modification of the model, or their output, from you also gets a copy of these terms or the URL for them above. + +No Other Rights +These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses. + +Patent Defense +If you make any written claim that the model infringes or contributes to infringement of any patent, your licenses for the model granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company. + +Violations +The first time you are notified in writing that you have violated any of these terms, or done anything with the model or its output that is not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 30 days of receiving notice. Otherwise, all your licenses end immediately. + +No Liability +AS FAR AS THE LAW ALLOWS, THE MODEL AND ITS OUTPUT COME AS IS, WITHOUT ANY WARRANTY OR CONDITION, AND THE LICENSOR WILL NOT BE LIABLE TO YOU FOR ANY DAMAGES ARISING OUT OF THESE TERMS OR THE USE OR NATURE OF THE MODEL OR ITS OUTPUT, UNDER ANY KIND OF LEGAL CLAIM. IF THIS PROVISION IS NOT ENFORCEABLE IN YOUR JURISDICTION, YOUR LICENSES ARE VOID. + +Definitions +The licensor is the individual or entity offering these terms, and the model is the model the licensor makes available under these terms, including any documentation or similar information about the model. + +You refers to the individual or entity agreeing to these terms. + +Your company is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect. + +Your licenses are all the licenses granted to you under these terms. + +Use means anything you do with the model or its output requiring one of your licenses. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..049bab3 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# VoiceCraft: Zero-Shot Speech Editing and Text-to-Speech in the Wild +[Demo](https://jasonppy.github.io/VoiceCraft_web) [Paper](https://jasonppy.github.io/assets/pdfs/VoiceCraft.pdf) + +TL;DR: +VoiceCraft is a token infilling neural codec language model, that achieves state-of-the-art performance on both **speech editing** and **zero-shot text-to-speech (TTS)** on in-the-wild data including audiobooks, internet videos, and podcasts. + +To clone or edit an unseen voice, VoiceCraft needs only a few seconds of reference. + + +## TODO +The TODOs left will be completed by the end of March 2024. +- [x] Codebase upload +- [x] Environment setup +- [x] Inference demo for speech editing and TTS +- [] Upload model weights +- [] Training guidance +- [] Upload the RealEdit dataset + +## Environment setup +```bash +conda create -n voicecraft python=3.9.16 +conda activate voicecraft + +pip install torch==2.0.1 torchaudio==2.0.2 # this assumes your system is compatible with CUDA 11.7, otherwise checkout https://pytorch.org/get-started/previous-versions/#v201 +apt-get install ffmpeg # if you don't already have ffmpeg installed +pip install -e git+https://github.com/facebookresearch/audiocraft.git@c5157b5bf14bf83449c17ea1eeb66c19fb4bc7f0#egg=audiocraft +apt-get install espeak-ng # backend for the phonemizer installed below +pip install phonemizer==3.2.1 +pip install tensorboard +pip install datasets==2.12.0 +# install MFA for getting forced-alignment, this could take a few minutes +conda install -c conda-forge montreal-forced-aligner=2.2.17 openfst=1.8.2 kaldi=5.5.1068 +# conda install pocl # above gives an warning for installing pocl, not sure if really need this + +# to run ipynb +conda install -n voicecraft ipykernel --update-deps --force-reinstall +``` + +## Inference Examples +Checkout [`inference_speech_editing.ipynb`](./inference_speech_editing.ipynb) and [`inference_tts.ipynb`](./inference_tts.ipynb) + +## License +The codebase is under CC BY-NC-SA 4.0 ([LICENSE-CODE](./LICENSE-CODE)), and the model weights are under Coqui Public Model License 1.0.0 ([LICENSE-MODEL](./LICENSE-MODEL)). Note that we use some of the code from other repository that are under different licenses: `./models/codebooks_patterns.py` is under MIT license; `./models/modules`, `./steps/optim.py`, `data/tokenizer.py` are under Apache License, Version 2.0; the phonemizer we used is under GNU 3.0 License. For drop-in replacement of the phonemizer (i.e. text to IPA phoneme mapping), try [g2p](https://github.com/roedoejet/g2p) (MIT License) or [OpenPhonemizer](https://github.com/NeuralVox/OpenPhonemizer) (BSD-3-Clause Clear), although these are not tested. + + + +## Acknowledgement +We thank Feiteng for his [VALL-E reproduction](https://github.com/lifeiteng/vall-e), and we thank audiocraft team for open-sourcing [encodec](https://github.com/facebookresearch/audiocraft). + +## Citation +``` +@article{peng2024voicecraft, + author = {Peng, Puyuan and Huang, Po-Yao and Li, Daniel and Mohamed, Abdelrahman and Harwath, David}, + title = {VoiceCraft: Zero-Shot Speech Editing and Text-to-Speech in the Wild}, + journal = {arXiv}, + year = {2024}, +} +``` + +## Disclaimer +Any organization or individual is prohibited from using any technology mentioned in this paper to generate or edit someone's speech without his/her consent, including but not limited to government leaders, political figures, and celebrities. If you do not comply with this item, you could be in violation of copyright laws. + diff --git a/config.py b/config.py new file mode 100644 index 0000000..466c6ad --- /dev/null +++ b/config.py @@ -0,0 +1,86 @@ +import argparse + + +def MyParser(): + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + # general training + parser.add_argument("--seed", type=int, default=1) + parser.add_argument("--precision", type=str, default="float16") + parser.add_argument("--num_workers", type=int, default=8) + parser.add_argument("--resume", action="store_true", default=False) + parser.add_argument("--tb_write_every_n_steps", type=int, default=100) + parser.add_argument("--print_every_n_steps", type=int, default=400) + parser.add_argument("--val_every_n_steps", type=int, default=800) + parser.add_argument("--lr", type=float, default=0.05) + parser.add_argument("--batch_size", type=int, default=100, help="this is the effective batch size, no matter whether using gradient_accumulation_steps, not used if we specified max_num_tokens") + parser.add_argument("--max_num_tokens", type=int, default=100000, help="max number of encodec tokens per gpu, this is only used when using dynamic batching, will ignore batch size. Note this is the final effective batch size per GPU, i.e. gradient accumulated batch size per gpu") + parser.add_argument("--val_max_num_tokens", type=int, default=None, help="FOR validation") + parser.add_argument("--num_buckets", type=int, default=6, help='used for dynamic batching, bucketing the samples based on the number of tokens') + parser.add_argument("--dynamic_batching", type=int, default=0) + parser.add_argument("--weight_decay", type=float, default=1e-2) + parser.add_argument("--warmup_fraction", type=float, default=0.01, help="use linear warmup, the proportion of the training steps that are used for warming up") + parser.add_argument("--num_epochs", type=int, default=10) + parser.add_argument("--num_steps", type=int, default=None, help="if not None, will ignore n_epochs and use num_steps as the total number of amount of training, can try e.g. 400000 i.e. 400k steps") + parser.add_argument("--gradient_accumulation_steps", type=int, default=1) + parser.add_argument("--gradient_clip_val", type=float, default=1.0, help="the value for torch.nn.utils.clip_grad_norm_(), not used if we use ScaledAdam optimizer") + parser.add_argument("--early_stop_step", type=int, default=3200, help="stop training after this many steps of non-improvement") + parser.add_argument("--early_stop_threshold", type=float, default=-1.0, help="early stop after the improvement is below this threshold for certain number of steps") + + # optimizer focused + parser.add_argument("--optimizer_name", type=str, default="AdamW", help="can also use ScaledAdam, in which case we'll also use the Eden scheduler") + parser.add_argument("--reduce_lr_start_step", type=int, default=3000, help='after which significantly reduce the lr. a param for the eden optimizer') + parser.add_argument("--pseudo_epoch_size", type=int, default=3000, help="only use for Eden scheduler.") + parser.add_argument("--reduce_lr_start_epoch", type=int, default=4) + parser.add_argument("--clipping_update_period", type=int, default=600) + + + # path + parser.add_argument("--exp_dir", type=str, default=None, help="will be combined with dataset name") + parser.add_argument("--dataset", type=str, help="e.g. 'libritts', 'gigaspeech', they are folder name in the data dir also") + parser.add_argument("--dataset_dir", type=str, help="need to be compatible with corresponding dataset py file") + parser.add_argument("--phn_folder_name", type=str, default="phonemes", help="for libritts I also have arpa phns, in which case should be phonemes_arpa") + parser.add_argument("--encodec_folder_name", type=str, default="encodec_16khz_4codebooks", help="folder where encodec codes are stored") + parser.add_argument("--manifest_name", type=str, default="manifest", help="metadata filename") + + # data focused + parser.add_argument("--pad_x", type=int, default=1, help="whether or not always pad x to have text_max_length. select 1 to get the maximal memory consumption, but the actual case should be smaller, better to have it being 0") + parser.add_argument("--audio_max_length", type=float, default=20, help="in second, crop or drop the audio is length is longer than this") + parser.add_argument("--audio_min_length", type=float, default=2, help="in second, drop the audio if length is shorter than this") + parser.add_argument("--text_max_length", type=int, default=400, help='if too long, we crop or drop') + parser.add_argument("--text_min_length", type=float, default=10, help="if too short, will drop") + parser.add_argument("--encodec_sr", type=int, default=50, help="for my encodec that takes 16kHz audio with a downsample rate of 320, the codec sample rate is 50Hz, i.e. 50 codes (x n_codebooks) per second") + parser.add_argument("--drop_long", type=int, default=0, help="if this is true, will drop example whose encodec sequence or phone sequence is too long, rather than cropping, to reduce hellucination") + + # encodec and token rearrangement + parser.add_argument('--mask_len_min', type=int, default=1, help='Minimum mask length') + parser.add_argument('--mask_len_max', type=int, default=600, help='Maximum mask length') + parser.add_argument("--eos", type=int, default=-1, help="this is to be used with reduced_eog, where we end the utterance with eos, and end the generated segment with eog, also when this is used, the n_special should be 4") + parser.add_argument("--reduced_eog", type=int, default=0, help="for the non-final segments, do not insert eog at the end, this could hopefully solve the early stopping issue when doing tts") + parser.add_argument("--special_first", type=int, default=0, help="if 1, need to have special tokens to be the first few tokens, e.g. 0, 1, 2, which means we need to adjust the preprocessing and postprocessing of the encodec codes. note that we hard coded to have 3 special tokens") + parser.add_argument("--n_special", type=int, default=3, help="empty, eog, pad, (eos)") + parser.add_argument("--codebook_weight", type=str, default=None, help="e.g. ['5','1','0.5','0.1']") + parser.add_argument("--max_mask_portion",type=float,default=0.7,help="should mask a utterance for more than this portion") + parser.add_argument("--max_n_spans", type=int, default=3, help='maximal number of spans, only use when using multicm3, this is used to decide number of mask_embedding, and max clamp value if use Poisson distribution, if use uniform distribution to sample number of spans if will be uniform(1,max_n_spans)') + parser.add_argument("--shuffle_mask_embedding", type=int, default=0, help="whether shuffle the mask embedding, so that mask:0 is not the most well trained, default is not shuffling. The default has it's benefit, as it make sure that mask:0 always appear the first") + parser.add_argument("--mask_sample_dist", type=str, default="poisson1", help="uniform or poissonx, e.g. poisson1, meaning the parameter lambda is 1, it will most likely sample 1 masks") + parser.add_argument("--min_gap", type=int, default=5, help="after sampled starts, delete later one if it closer to the former start than the min_gap") + parser.add_argument('--n_codebooks', type=int, default=4) + parser.add_argument('--text_vocab_size', type=int, default=100, help='Size of text vocabulary') + parser.add_argument('--text_pad_token', type=int, default=100, help='padding of the text tokens, not attended') + parser.add_argument('--audio_vocab_size', type=str, default='2048', help="Size of audio vocabulary") + parser.add_argument("--empty_token", default=2048, type=int, help="indicating the no token at the position for the codebook") + parser.add_argument('--eog', type=int, default=2049, help='End of generation token') + parser.add_argument('--audio_pad_token', type=int, default=2050, help='padding of the encodec codes, not attended') + + # model focused + parser.add_argument('--d_model', type=int, default=2048, help='Model dimension') + parser.add_argument('--audio_embedding_dim', type=int, default=2048, help='dimension for encodec continues embedding (before being quantized)') + parser.add_argument('--text_embedding_dropout', type=float, default=0.1, help='Dropout for text embedding') + parser.add_argument('--audio_embedding_dropout', type=float, default=0, help='Dropout for audio embedding') + parser.add_argument('--text_positional_embedding_dropout', type=float, default=0.1, help='Dropout for text positional embedding') + parser.add_argument('--audio_positional_embedding_dropout', type=float, default=0.1, help='Dropout for audio positional embedding') + parser.add_argument('--trm_dropout', type=float, default=0.1, help='Dropout for transformer') + parser.add_argument('--nhead', type=int, default=16, help='Number of attention heads') + parser.add_argument('--num_decoder_layers', type=int, default=16, help='Number of decoder layers') + parser.add_argument('--load_model_from', type=str, default=None, help='Path to load model from, this will be effective last, so will overwrite all previous load, including resume') + return parser \ No newline at end of file diff --git a/data/__init__.py b/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/data/giga_preprocessing/encodec_encode.py b/data/giga_preprocessing/encodec_encode.py new file mode 100644 index 0000000..f2a9915 --- /dev/null +++ b/data/giga_preprocessing/encodec_encode.py @@ -0,0 +1,160 @@ +import argparse +def parse_args(): + parser = argparse.ArgumentParser(description="encode the librilight dataset using encodec model") + parser.add_argument("--manifest_root", type=str, default="/home/pyp/audiocraft/egs/gigaspeech", help="this the dir of the audiocraft manifest!") + parser.add_argument('--audio_dir', type=str, default="/data/scratch/pyp/datasets/gigaspeech_flac", help="Path dirs of the flac audio files") + parser.add_argument('--save_dir', type=str, default="/data/scratch/pyp/datasets/gigaspeech_phn_enc_manifest/xl", help="path to the manifest, phonemes, and encodec codes dirs") + parser.add_argument('--encodec_model_path', type=str, default="/data/scratch/pyp/exp_pyp/audiocraft/encodec/xps/6f79c6a8/checkpoint.th") + parser.add_argument('--n_workers', type=int, default=32, help="Number of parallel worker processes") + parser.add_argument('--batch_size', type=int, default=64, help="batch size for encodec encoding, decrease it if OOM. This is the sum of batch size *over each gpu*, so increase it if you are using more gpus") + parser.add_argument('--model_sr', type=int, default=16000, help='encodec input audio sample rate') + parser.add_argument('--downsample_rate', type=int, default=320, help='encodec downsample rate') + parser.add_argument('--model_code_sr', type=int, default=50, help='encodec model code sample rate') + parser.add_argument('--len_cap', type=float, default=35.0, help='will drop audios that are longer than this number') + return parser.parse_args() + +if __name__ == "__main__": + import logging + formatter = ( + "%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d || %(message)s" + ) + logging.basicConfig(format=formatter, level=logging.INFO) + + import os + import numpy as np + import torch + import torchaudio + import tqdm + import time + + args = parse_args() + + manifest_dir = args.manifest_root # this dir is scp-ed + audio_dir = args.audio_dir # this is scp-ed flac dir + encodec_signature = args.encodec_model_path.split("/")[-2] + save_codes_dir = os.path.join(args.save_dir, f"encodec_16khz_{encodec_signature}") + os.makedirs(save_codes_dir, exist_ok=True) + + + # model_sr = 16000 + # downsample_rate = 320 + # model_code_sr = 50 + def sort_by_audio_len(lens): + inds = np.argsort(lens).tolist() + logging.info(f"longest: {lens[inds[-1]]/args.downsample_rate} encodec codes, {lens[inds[-1]]/args.model_sr:.2f} sec.") + logging.info(f"shortest: {lens[inds[0]]/args.downsample_rate} encodec codes, {lens[inds[0]]/args.model_sr:.2f} sec.") + logging.info(f"median: {lens[inds[len(inds)//2]]/args.downsample_rate} encodec codes, {lens[inds[len(inds)//2]]/args.model_sr:.2f} sec.") + logging.info(f"95 percentile longest: {lens[inds[int(len(inds)*0.95)]]/args.downsample_rate} encodec codes, {lens[inds[int(len(inds)*0.95)]]/args.model_sr:.2f} sec.") + return inds[::-1] + + def write_array_to_txt_file(array, filename): + with open(filename, 'w') as f: + for a in array[:-1]: + f.write(' '.join(map(str, a))+'\n') + f.write(' '.join(map(str, array[-1]))) + + + + class mydataset(torch.utils.data.Dataset): + def __init__(self, split): + super().__init__() + # self.data = gs[split] + self.split = split + self.audio_root = audio_dir + manifest_fn = os.path.join(manifest_dir, split+".txt") + with open(manifest_fn, "r") as rf: + self.data = [l.strip().split("\t") for l in rf.readlines()] + def __len__(self): + return len(self.data) + def __getitem__(self, ind): + try: + afn = self.data[ind][0] + fn = os.path.join(self.audio_root, afn) + audio, sr = torchaudio.load(fn) + assert sr == args.model_sr, sr + except Exception as e: + logging.info(f"{e}") + return None, None, None + assert audio.ndim==2 and audio.shape[0] == 1, audio.shape + return audio.type(torch.float32).squeeze(0), audio.shape[-1], os.path.basename(afn).split(".")[0] + def collate(self, batch): + lens, audios, segment_ids = [], [], [] + for item in batch: + if item[0] != None: + audios.append(item[0]) + lens.append(item[1]) + segment_ids.append(item[2]) + return audios, lens, segment_ids + + # load the encodec model + from audiocraft.solvers import CompressionSolver + model = CompressionSolver.model_from_checkpoint(args.encodec_model_path) + model = model.cuda() + model = model.eval() + model = torch.nn.DataParallel(model) + + + # setup dataloader + mega_batch_size = 2100 + batch_size = args.batch_size + train_dataset = mydataset('train') + train_loader = torch.torch.utils.data.DataLoader(train_dataset, batch_size=mega_batch_size, shuffle=False, drop_last=False, num_workers=args.n_workers, collate_fn=train_dataset.collate) + validation_dataset = mydataset('validation') + validation_loader = torch.torch.utils.data.DataLoader(validation_dataset, batch_size=mega_batch_size, shuffle=False, drop_last=False, num_workers=args.n_workers, collate_fn=validation_dataset.collate) + test_dataset = mydataset('test') + test_loader = torch.torch.utils.data.DataLoader(test_dataset, batch_size=mega_batch_size, shuffle=False, drop_last=False, num_workers=args.n_workers, collate_fn=test_dataset.collate) + splits = ['validation', 'test', 'train'] + loaders = [validation_loader, test_loader, train_loader] + # splits = ['validation'] # NOTE this is for debug, for example, see if the + # loaders = [validation_loader] + for split, loader in zip(splits, loaders): + skip = 0 + logging.info(f"now processing split {split}...") + mega_n_steps = int(np.ceil(len(loader.dataset) / mega_batch_size)) + # mega_n_steps = int(np.ceil(len(gs) / mega_batch_size)) + logging.info(f"partition the split {split} into {mega_n_steps} parts, each has {mega_batch_size} samples") + # with open(mani_fn, "a") as mani_wf: # resume from where we failed + for m, mega_batch in enumerate(loader): + logging.info(f"====================================") + logging.info(f"====================================") + logging.info(f"now processing mega step {m+1}/{mega_n_steps}") + lengths = np.array(mega_batch[1]) + sorted_inds = sort_by_audio_len(lengths) + for j in range(len(sorted_inds))[::-1]: + if lengths[sorted_inds[j]] < args.model_sr*0.2 or lengths[sorted_inds[j]] > args.model_sr*args.len_cap: # skip samples that are too short (shorter than 0.2s), or too big (bigger than 80s) + skip += 1 + del sorted_inds[j] + + n_steps = int(np.ceil(len(sorted_inds) / batch_size)) + for n in tqdm.tqdm(range(n_steps), disable=True): + inds_used = sorted_inds[n*batch_size:(n+1)*batch_size] + wav_batch = [mega_batch[0][id] for id in inds_used] + all_lens = [mega_batch[1][id] for id in inds_used] + segment_id_batch = [mega_batch[2][id] for id in inds_used] + # print(segment_id_batch) + padded_wav = torch.nn.utils.rnn.pad_sequence(wav_batch, batch_first=True).unsqueeze(1) # [B, T] -> [B, 1, T] + with torch.no_grad(): + if max(all_lens) > 300000 and len(all_lens) > 1: # NOTE decrease this (300000) if OOM, or chunk it into more than 2 forward passes + codes = [] + inwav = padded_wav.cuda() + codes.append(model(inwav[:len(inwav)//2], encode=True)[0].cpu()) + codes.append(model(inwav[len(inwav)//2:], encode=True)[0].cpu()) + codes = torch.cat(codes, dim=0) + else: + encoded_frames = model(padded_wav.cuda(), encode=True) # wav needs to have shape [B, C, T], C is model.channels, which is 1 for the 24kHz encodec model + # logging.info(f"encoded_frames: {encoded_frames[0].shape}") + codes = encoded_frames[0].cpu() + + for i, length in enumerate(all_lens): + save_fn = os.path.join(save_codes_dir, segment_id_batch[i]+".txt") + actual_len = round(length / args.downsample_rate) # 320 is downsample rate for this model + cur_code = codes[i].tolist() if type(codes) == list else codes[i, :, :actual_len].tolist() + write_array_to_txt_file(cur_code, save_fn) + + # mani_wf.write(f"0\t{segment_id_batch[i]}\t{len(cur_code[0])}\n") # write to manifest file + # if i == 10: + # raise + # break + # logging.info(f"split {split} has {len(gs[split])} samples in total, skipped {skip} due to forbiden words") + logging.info(f"split {split} has {len(loader.dataset)} samples in total, skipped {skip} due to utterance being too long or too short") + # break \ No newline at end of file diff --git a/data/gigaspeech.py b/data/gigaspeech.py new file mode 100644 index 0000000..0d855a6 --- /dev/null +++ b/data/gigaspeech.py @@ -0,0 +1,158 @@ +import os +import torch +import random +import copy +import logging +import shutil + +class dataset(torch.utils.data.Dataset): + def __init__(self, args, split): + super().__init__() + self.args = args + self.split = split + assert self.split in ['train', 'validation', 'test'] + manifest_fn = os.path.join(self.args.dataset_dir, self.args.manifest_name, self.split+".txt") + + with open(manifest_fn, "r") as rf: + data = [l.strip().split("\t") for l in rf.readlines()] + lengths_list = [int(item[-1]) for item in data] + self.data = [] + self.lengths_list = [] + for d, l in zip(data, lengths_list): + if l >= self.args.encodec_sr*self.args.audio_min_length: + if self.args.drop_long and l > self.args.encodec_sr*self.args.audio_max_length: + continue + self.data.append(d) + self.lengths_list.append(l) + logging.info(f"number of data points for {self.split} split: {len(self.lengths_list)}") + + # phoneme vocabulary + vocab_fn = os.path.join(self.args.dataset_dir,"vocab.txt") + shutil.copy(vocab_fn, os.path.join(self.args.exp_dir, "vocab.txt")) + with open(vocab_fn, "r") as f: + temp = [l.strip().split(" ") for l in f.readlines() if len(l) != 0] + self.phn2num = {item[1]:int(item[0]) for item in temp} + + self.symbol_set = set(["", "", "", ""]) + + def __len__(self): + return len(self.lengths_list) + + def _load_phn_enc(self, index): + item = self.data[index] + pf = os.path.join(self.args.dataset_dir, self.args.phn_folder_name, item[1]+".txt") + ef = os.path.join(self.args.dataset_dir, self.args.encodec_folder_name, item[1]+".txt") + try: + with open(pf, "r") as p, open(ef, "r") as e: + phns = [l.strip() for l in p.readlines()] + assert len(phns) == 1, phns + x = [self.phn2num[item] for item in phns[0].split(" ") if item not in self.symbol_set] # drop ["", "", "", ""], as they are not in training set annotation + encos = [l.strip().split() for k, l in enumerate(e.readlines()) if k < self.args.n_codebooks] + + assert len(encos) == self.args.n_codebooks, ef + if self.args.special_first: + y = [[int(n)+self.args.n_special for n in l] for l in encos] + else: + y = [[int(n) for n in l] for l in encos] + if self.args.training_stage == 1 and not self.args.valle and not (self.args.musicgen or self.args.valle_orig): + y = y[:1] + except Exception as e: + logging.info(f"loading failed for {pf} and {ef}, maybe files don't exist or are corrupted") + logging.info(f"error message: {e}") + return [], [[]] + + return x, y + + def __getitem__(self, index): + x, y = self._load_phn_enc(index) + x_len, y_len = len(x), len(y[0]) + + if x_len == 0 or y_len == 0: + return { + "x": None, + "x_len": None, + "y": None, + "y_len": None, + "y_mask_interval": None, # index y_mask_interval[1] is the position of start_of_continue token + "extra_mask_start": None # this is only used in VE1 + } + while y_len < self.args.encodec_sr*self.args.audio_min_length: + assert not self.args.dynamic_batching + index = random.choice(range(len(self))) # regenerate an index + x, y = self._load_phn_enc(index) + x_len, y_len = len(x), len(y[0]) + if self.args.drop_long: + while x_len > self.args.text_max_length or y_len > self.args.encodec_sr*self.args.audio_max_length: + index = random.choice(range(len(self))) # regenerate an index + x, y = self._load_phn_enc(index) + x_len, y_len = len(x), len(y[0]) + + ### padding and cropping below ### + ### padding and cropping below ### + # adjust the length of encodec codes, pad to max_len or randomly crop + orig_y_len = copy.copy(y_len) + max_len = int(self.args.audio_max_length * self.args.encodec_sr) + if y_len > max_len: + audio_start = random.choice(range(0, y_len-max_len)) + for i in range(len(y)): + y[i] = y[i][audio_start:(audio_start+max_len)] + y_len = max_len + else: + audio_start = 0 + if not self.args.dynamic_batching: + pad = [0] * (max_len - y_len) if self.args.sep_special_token else [self.args.audio_pad_token] * (max_len - y_len) + for i in range(len(y)): + y[i] = y[i] + pad + + # adjust text + # if audio is cropped, and text is longer than max, crop max based on how audio is cropped + if audio_start > 0 and len(x) > self.args.text_max_length: # if audio is longer than max and text is long than max, start text the way audio started + x = x[int(len(x)*audio_start/orig_y_len):] + if len(x) > self.args.text_max_length: # if text is still longer than max, cut the end + x = x[:self.args.text_max_length] + + x_len = len(x) + if x_len > self.args.text_max_length: + text_start = random.choice(range(0, x_len - self.args.text_max_length)) + x = x[text_start:text_start+self.args.text_max_length] + x_len = self.args.text_max_length + elif self.args.pad_x and x_len <= self.args.text_max_length: + pad = [0] * (self.args.text_max_length - x_len) if self.args.sep_special_token else [self.args.text_pad_token] * (self.args.text_max_length - x_len) + x = x + pad + ### padding and cropping above ### + ### padding and cropping above ### + + return { + "x": torch.LongTensor(x), + "x_len": x_len, + "y": torch.LongTensor(y), + "y_len": y_len + } + + + def collate(self, batch): + out = {key:[] for key in batch[0]} + for item in batch: + if item['x'] == None: # deal with load failure + continue + for key, val in item.items(): + out[key].append(val) + res = {} + if self.args.pad_x: + res["x"] = torch.stack(out["x"], dim=0) + else: + res["x"] = torch.nn.utils.rnn.pad_sequence(out["x"], batch_first=True, padding_value=0 if self.args.sep_special_token else self.args.text_pad_token) + res["x_lens"] = torch.LongTensor(out["x_len"]) + if self.args.dynamic_batching: + if out['y'][0].ndim==2: + res['y'] = torch.nn.utils.rnn.pad_sequence([item.transpose(1,0) for item in out['y']],padding_value=0 if self.args.sep_special_token else self.args.audio_pad_token) + res['y'] = res['y'].permute(1,2,0) # T B K -> B K T + else: + assert out['y'][0].ndim==1, out['y'][0].shape + res['y'] = torch.nn.utils.rnn.pad_sequence(out['y'], batch_first=True, padding_value=0 if self.args.sep_special_token else self.args.audio_pad_token) + else: + res['y'] = torch.stack(out['y'], dim=0) + res["y_lens"] = torch.LongTensor(out["y_len"]) + res["text_padding_mask"] = torch.arange(res['x'][0].shape[-1]).unsqueeze(0) >= res['x_lens'].unsqueeze(1) + res["audio_padding_mask"] = torch.arange(res['y'][0].shape[-1]).unsqueeze(0) >= res['y_lens'].unsqueeze(1) + return res \ No newline at end of file diff --git a/data/tokenizer.py b/data/tokenizer.py new file mode 100644 index 0000000..1495120 --- /dev/null +++ b/data/tokenizer.py @@ -0,0 +1,149 @@ +# cp from https://github.com/lifeiteng/vall-e/blob/main/valle/data/tokenizer.py +# Copyright 2023 (authors: Feiteng Li) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from dataclasses import asdict, dataclass +from typing import Any, Dict, List, Optional, Pattern, Union + +import numpy as np +import torch +import torchaudio +# from lhotse.features import FeatureExtractor +# from lhotse.utils import Seconds, compute_num_frames +from phonemizer.backend import EspeakBackend +from phonemizer.backend.espeak.language_switch import LanguageSwitch +from phonemizer.backend.espeak.words_mismatch import WordMismatch +from phonemizer.punctuation import Punctuation +from phonemizer.separator import Separator + + + +class TextTokenizer: + """Phonemize Text.""" + + def __init__( + self, + language="en-us", + backend="espeak", + separator=Separator(word="_", syllable="-", phone="|"), + preserve_punctuation=True, + punctuation_marks: Union[str, Pattern] = Punctuation.default_marks(), + with_stress: bool = False, + tie: Union[bool, str] = False, + language_switch: LanguageSwitch = "keep-flags", + words_mismatch: WordMismatch = "ignore", + ) -> None: + phonemizer = EspeakBackend( + language, + punctuation_marks=punctuation_marks, + preserve_punctuation=preserve_punctuation, + with_stress=with_stress, + tie=tie, + language_switch=language_switch, + words_mismatch=words_mismatch, + ) + + self.backend = phonemizer + self.separator = separator + + def to_list(self, phonemized: str) -> List[str]: + fields = [] + for word in phonemized.split(self.separator.word): + # "ɐ m|iː|n?" ɹ|ɪ|z|ɜː|v; h|ɪ|z. + pp = re.findall(r"\w+|[^\w\s]", word, re.UNICODE) + fields.extend( + [p for p in pp if p != self.separator.phone] + + [self.separator.word] + ) + assert len("".join(fields[:-1])) == len(phonemized) - phonemized.count( + self.separator.phone + ) + return fields[:-1] + + def __call__(self, text, strip=True) -> List[List[str]]: + if isinstance(text, str): + text = [text] + + phonemized = self.backend.phonemize( + text, separator=self.separator, strip=strip, njobs=1 + ) + return [self.to_list(p) for p in phonemized] + + +def tokenize_text(tokenizer: TextTokenizer, text: str) -> List[str]: + phonemes = tokenizer([text.strip()]) + return phonemes[0] # k2symbols + +def convert_audio(wav: torch.Tensor, sr: int, target_sr: int, target_channels: int): + assert wav.shape[0] in [1, 2], "Audio must be mono or stereo." + if target_channels == 1: + wav = wav.mean(0, keepdim=True) + elif target_channels == 2: + *shape, _, length = wav.shape + wav = wav.expand(*shape, target_channels, length) + elif wav.shape[0] == 1: + wav = wav.expand(target_channels, -1) + wav = torchaudio.transforms.Resample(sr, target_sr)(wav) + return wav + +class AudioTokenizer: + """EnCodec audio.""" + + def __init__( + self, + device: Any = None, + signature = None + ) -> None: + from audiocraft.solvers import CompressionSolver + model = CompressionSolver.model_from_checkpoint(signature) + self.sample_rate = model.sample_rate + self.channels = model.channels + + if not device: + device = torch.device("cpu") + if torch.cuda.is_available(): + device = torch.device("cuda:0") + + self._device = device + + self.codec = model.to(device) + + @property + def device(self): + return self._device + + def encode(self, wav: torch.Tensor) -> torch.Tensor: + codes = self.codec.encode(wav.to(self.device)) + return [(codes[0], None)] + + def decode(self, frames: torch.Tensor) -> torch.Tensor: + frames = frames[0][0] # [1,4,T] + return self.codec.decode(frames) + + + +def tokenize_audio(tokenizer: AudioTokenizer, audio_path: str, offset = -1, num_frames=-1): + # Load and pre-process the audio waveform + if offset != -1 and num_frames!=-1: + wav, sr = torchaudio.load(audio_path, frame_offset=offset, num_frames=num_frames) + else: + wav, sr = torchaudio.load(audio_path) + wav = convert_audio(wav, sr, tokenizer.sample_rate, tokenizer.channels) + wav = wav.unsqueeze(0) + + # Extract discrete codes from EnCodec + with torch.no_grad(): + encoded_frames = tokenizer.encode(wav) + return encoded_frames diff --git a/demo/84_121550_000074_000000.wav b/demo/84_121550_000074_000000.wav new file mode 100644 index 0000000000000000000000000000000000000000..ed169746706a85999f17f2b467d601468c297478 GIT binary patch literal 507578 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 z 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%0DMVSykv;eQ$+I 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�AxnR!o&F;B((;D32RZSw+| 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 z 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

ZX<+tK(I-7R=u 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 len(new_list), f"the edit type is deletion, but new is not shorter than original:\n new: {new}\n orig: {orig}" + diff = len(orig_list) - len(new_list) + for i, (o, n) in enumerate(zip(orig_list, new_list)): + if o != n: # assume the index of the first different word is the starting index of the orig_span + + orig_span = [i, i + diff - 1] # assume that the indices are starting and ending index of the deleted part + new_span = [i-1, i] # but for the new span, the starting and ending index is the two words that surround the deleted part + flag = True + break + + + elif editType == "insertion": + assert len(orig_list) < len(new_list), f"the edit type is insertion, but the new is not longer than the original:\n new: {new}\n orig: {orig}" + diff = len(new_list) - len(orig_list) + for i, (o, n) in enumerate(zip(orig_list, new_list)): + if o != n: # insertion is just the opposite of deletion + new_span = [i, i + diff - 1] # NOTE if only inserted one word, s and e will be the same + orig_span = [i-1, i] + flag = True + break + + elif editType == "substitution": + new_span = [] + orig_span = [] + for i, (o, n) in enumerate(zip(orig_list, new_list)): + if o != n: + new_span = [i] + orig_span = [i] + break + assert len(new_span) == 1 and len(orig_span) == 1, f"new_span: {new_span}, orig_span: {orig_span}" + for j, (o, n) in enumerate(zip(orig_list[::-1], new_list[::-1])): + if o != n: + new_span.append(len(new_list) - j -1) + orig_span.append(len(orig_list) - j - 1) + flag = True + break + else: + raise RuntimeError(f"editType unknown: {editType}") + + if not flag: + raise RuntimeError(f"wrong editing with the specified edit type:\n original: {orig}\n new: {new}\n, editType: {editType}") + + return orig_span, new_span \ No newline at end of file diff --git a/inference_speech_editing.ipynb b/inference_speech_editing.ipynb new file mode 100644 index 0000000..64340f7 --- /dev/null +++ b/inference_speech_editing.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ[\"CUDA_DEVICE_ORDER\"]=\"PCI_BUS_ID\" \n", + "os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"0\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/pyp/miniconda3/envs/voicecraft/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + } + ], + "source": [ + "# import libs\n", + "import torch\n", + "import torchaudio\n", + "\n", + "from data.tokenizer import (\n", + " AudioTokenizer,\n", + " TextTokenizer,\n", + ")\n", + "\n", + "from models import voicecraft\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# hyperparameters for inference\n", + "left_margin = 0.08\n", + "right_margin = 0.08\n", + "seed = 1\n", + "codec_audio_sr = 16000\n", + "codec_sr = 50\n", + "top_k = 0\n", + "top_p = 0.8\n", + "temperature = 1\n", + "kvcache = 0\n", + "silence_tokens = [1388,1898,131]\n", + "stop_repetition = -1 # do not stop repetition on silence\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "\n", + "# point to the original file or record the file\n", + "# write down the transcript for the file, or run whisper to get the transcript (and you can modify it if it's not accurate), save it as a .txt file\n", + "orig_audio = \"./demo/84_121550_000074_000000.wav\"\n", + "orig_transcript = \"But when I had approached so near to them The common object, which the sense deceives, Lost not by distance any of its marks,\"\n", + "# move the audio and transcript to temp folder\n", + "temp_folder = \"./demo/temp\"\n", + "os.makedirs(temp_folder, exist_ok=True)\n", + "os.system(f\"cp {orig_audio} {temp_folder}\")\n", + "filename = os.path.splitext(orig_audio.split(\"/\")[-1])[0]\n", + "with open(f\"{temp_folder}/{filename}.txt\", \"w\") as f:\n", + " f.write(orig_transcript)\n", + "# run MFA to get the alignment\n", + "align_temp = f\"{temp_folder}/mfa_alignments\"\n", + "os.makedirs(align_temp, exist_ok=True)\n", + "os.system(f\"mfa align -j 1 --output_format csv {temp_folder} english_us_arpa english_us_arpa {align_temp}\")\n", + "# if it fail, it could be because the audio is too hard for the alignment model, increasing the beam size usually solves the issue\n", + "# os.system(f\"mfa align -j 1 --output_format csv {temp_folder} english_us_arpa english_us_arpa {align_temp} --beam 1000 --retry_beam 2000\")\n", + "audio_fn = f\"{temp_folder}/{filename}.wav\"\n", + "transcript_fn = f\"{temp_folder}/{filename}.txt\"\n", + "align_fn = f\"{align_temp}/{filename}.csv\"\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:phonemizer:words count mismatch on 300.0% of the lines (3/1)\n" + ] + } + ], + "source": [ + "editTypes_set = set(['substitution', 'insertion', 'deletion'])\n", + "# propose what do you want the target modified transcript to be\n", + "target_transcript = \"But when I saw the mirage of the lake in the distance, which the sense deceives, Lost not by distance any of its marks,\"\n", + "edit_type = \"substitution\"\n", + "assert edit_type in editTypes_set, f\"Invalid edit type {edit_type}. Must be one of {editTypes_set}.\"\n", + "\n", + "# if you want to do a second modification on top of the first one, write down the second modification (target_transcript2, type_of_modification2)\n", + "# make sure the two modification do not overlap, if they do, you need to combine them into one modification\n", + "\n", + "# run the script to turn user input to the format that the model can take\n", + "from edit_utils import get_span\n", + "orig_span, new_span = get_span(orig_transcript, target_transcript, edit_type)\n", + "if orig_span[0] > orig_span[1]:\n", + " RuntimeError(f\"example {audio_fn} failed\")\n", + "if orig_span[0] == orig_span[1]:\n", + " orig_span_save = [orig_span[0]]\n", + "else:\n", + " orig_span_save = orig_span\n", + "if new_span[0] == new_span[1]:\n", + " new_span_save = [new_span[0]]\n", + "else:\n", + " new_span_save = new_span\n", + "\n", + "orig_span_save = \",\".join([str(item) for item in orig_span_save])\n", + "new_span_save = \",\".join([str(item) for item in new_span_save])\n", + "from inference_speech_editing_scale import get_mask_interval\n", + "\n", + "start, end = get_mask_interval(align_fn, orig_span_save, edit_type)\n", + "info = torchaudio.info(audio_fn)\n", + "audio_dur = info.num_frames / info.sample_rate\n", + "morphed_span = (max(start - left_margin, 1/codec_sr), min(end + right_margin, audio_dur)) # in seconds\n", + "\n", + "# span in codec frames\n", + "mask_interval = [[round(morphed_span[0]*codec_sr), round(morphed_span[1]*codec_sr)]]\n", + "mask_interval = torch.LongTensor(mask_interval) # [M,2], M==1 for now\n", + "\n", + "# load model, tokenizer, and other necessary files\n", + "ckpt_fn = \"/data/scratch/pyp/exp_pyp/VoiceCraft/gigaspeech/pretrained_830M/best_bundle.pth\"\n", + "encodec_fn = \"/data/scratch/pyp/exp_pyp/audiocraft/encodec/xps/6f79c6a8/checkpoint.th\"\n", + "ckpt = torch.load(ckpt_fn, map_location=\"cpu\")\n", + "model = voicecraft.VoiceCraft(ckpt[\"config\"])\n", + "model.load_state_dict(ckpt[\"model\"])\n", + "model.to(device)\n", + "model.eval()\n", + "\n", + "phn2num = ckpt['phn2num']\n", + "\n", + "text_tokenizer = TextTokenizer(backend=\"espeak\")\n", + "audio_tokenizer = AudioTokenizer(signature=encodec_fn) # will also put the neural codec model on gpu\n", + "\n", + "# run the model to get the output\n", + "from inference_speech_editing_scale import inference_one_sample\n", + "\n", + "decode_config = {'top_k': top_k, 'top_p': top_p, 'temperature': temperature, 'stop_repetition': stop_repetition, 'kvcache': kvcache, \"codec_audio_sr\": codec_audio_sr, \"codec_sr\": codec_sr, \"silence_tokens\": silence_tokens}\n", + "orig_audio, new_audio = inference_one_sample(model, ckpt[\"config\"], phn2num, text_tokenizer, audio_tokenizer, audio_fn, target_transcript, mask_interval, device, decode_config)\n", + " \n", + "# save segments for comparison\n", + "orig_audio, new_audio = orig_audio[0].cpu(), new_audio[0].cpu()\n", + "# logging.info(f\"length of the resynthesize orig audio: {orig_audio.shape}\")\n", + "\n", + "# output_dir\n", + "output_dir = \"./demo/generated_se\"\n", + "os.makedirs(output_dir, exist_ok=True)\n", + "\n", + "save_fn_new = f\"{output_dir}/{os.path.basename(audio_fn)[:-4]}_new_seed{seed}.wav\"\n", + "\n", + "torchaudio.save(save_fn_new, new_audio, codec_audio_sr)\n", + "\n", + "save_fn_orig = f\"{output_dir}/{os.path.basename(audio_fn)[:-4]}_orig.wav\"\n", + "if not os.path.isfile(save_fn_orig):\n", + " orig_audio, orig_sr = torchaudio.load(audio_fn)\n", + " if orig_sr != codec_audio_sr:\n", + " orig_audio = torchaudio.transforms.Resample(orig_sr, codec_audio_sr)(orig_audio)\n", + " torchaudio.save(save_fn_orig, orig_audio, codec_audio_sr)\n", + "\n", + "# if you get error importing T5 in transformers\n", + "# try \n", + "# pip uninstall Pillow\n", + "# pip install Pillow\n", + "# you are likely to get warning looks like WARNING:phonemizer:words count mismatch on 300.0% of the lines (3/1), this can be safely ignored" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "voicecraft", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/inference_speech_editing_scale.py b/inference_speech_editing_scale.py new file mode 100644 index 0000000..b034d95 --- /dev/null +++ b/inference_speech_editing_scale.py @@ -0,0 +1,226 @@ +import argparse, pickle +import logging +import os, random +import numpy as np +import torch +import torchaudio + +from data.tokenizer import ( + AudioTokenizer, + TextTokenizer, + tokenize_audio, + tokenize_text +) + +from models import voicecraft +import argparse, time, tqdm + +# this script only works for the musicgen architecture +def get_args(): + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--manifest_fn", type=str, default="path/to/eval_metadata_file") + parser.add_argument("--audio_root", type=str, default="path/to/audio_folder") + parser.add_argument("--exp_dir", type=str, default="path/to/model_folder") + parser.add_argument("--left_margin", type=float, default=0.08, help="extra space on the left to the word boundary") + parser.add_argument("--right_margin", type=float, default=0.08, help="extra space on the right to the word boundary") + parser.add_argument("--seed", type=int, default=1) + parser.add_argument("--codec_audio_sr", type=int, default=16000, help='the sample rate of audio that the codec is trained for') + parser.add_argument("--codec_sr", type=int, default=50, help='the sample rate of the codec codes') + parser.add_argument("--top_k", type=int, default=-1, help="sampling param") + parser.add_argument("--top_p", type=float, default=0.8, help="sampling param") + parser.add_argument("--temperature", type=float, default=1.0, help="sampling param") + parser.add_argument("--output_dir", type=str, default=None) + parser.add_argument("--device", type=str, default="cuda") + parser.add_argument("--signature", type=str, default=None, help="path to the encodec model") + parser.add_argument("--stop_repetition", type=int, default=2, help="used for inference, when the number of consecutive repetition of a token is bigger than this, stop it") + parser.add_argument("--kvcache", type=int, default=1, help='if true, use kv cache, which is 4-8x faster than without') + parser.add_argument("--silence_tokens", type=str, default="[1388,1898,131]", help="note that if you are not using the pretrained encodec 6f79c6a8, make sure you specified it yourself, rather than using the default") + return parser.parse_args() + +@torch.no_grad() +def inference_one_sample(model, model_args, phn2num, text_tokenizer, audio_tokenizer, audio_fn, target_text, mask_interval, device, decode_config): + # phonemize + text_tokens = [phn2num[phn] for phn in + tokenize_text( + text_tokenizer, text=target_text.strip() + ) if phn in phn2num + ] + text_tokens = torch.LongTensor(text_tokens).unsqueeze(0) + text_tokens_lens = torch.LongTensor([text_tokens.shape[-1]]) + + encoded_frames = tokenize_audio(audio_tokenizer, audio_fn) + original_audio = encoded_frames[0][0].transpose(2,1) # [1,T,K] + assert original_audio.ndim==3 and original_audio.shape[0] == 1 and original_audio.shape[2] == model_args.n_codebooks, original_audio.shape + logging.info(f"with direct encodec encoding before input, original audio length: {original_audio.shape[1]} codec frames, which is {original_audio.shape[1]/decode_config['codec_sr']:.2f} sec.") + + # forward + stime = time.time() + encoded_frames = model.inference( + text_tokens.to(device), + text_tokens_lens.to(device), + original_audio[...,:model_args.n_codebooks].to(device), # [1,T,8] + mask_interval=mask_interval.unsqueeze(0).to(device), + top_k=decode_config['top_k'], + top_p=decode_config['top_p'], + temperature=decode_config['temperature'], + stop_repetition=decode_config['stop_repetition'], + kvcache=decode_config['kvcache'], + silence_tokens=eval(decode_config['silence_tokens']) if type(decode_config['silence_tokens']) == str else decode_config['silence_tokens'], + ) # output is [1,K,T] + logging.info(f"inference on one sample take: {time.time() - stime:.4f} sec.") + if type(encoded_frames) == tuple: + encoded_frames = encoded_frames[0] + logging.info(f"generated encoded_frames.shape: {encoded_frames.shape}, which is {encoded_frames.shape[-1]/decode_config['codec_sr']} sec.") + + + # decode (both original and generated) + original_sample = audio_tokenizer.decode( + [(original_audio.transpose(2,1), None)] # [1,T,8] -> [1,8,T] + ) + generated_sample = audio_tokenizer.decode( + [(encoded_frames, None)] + ) + + return original_sample, generated_sample + +def get_model(exp_dir, device=None): + with open(os.path.join(exp_dir, "args.pkl"), "rb") as f: + model_args = pickle.load(f) + + logging.info("load model weights...") + model = voicecraft.VoiceCraft(model_args) + ckpt_fn = os.path.join(exp_dir, "best_bundle.pth") + ckpt = torch.load(ckpt_fn, map_location='cpu')['model'] + phn2num = torch.load(ckpt_fn, map_location='cpu')['phn2num'] + model.load_state_dict(ckpt) + del ckpt + logging.info("done loading weights...") + if device == None: + device = torch.device("cpu") + if torch.cuda.is_available(): + device = torch.device("cuda:0") + model.to(device) + model.eval() + return model, model_args, phn2num + + +def get_mask_interval(ali_fn, word_span_ind, editType): + with open(ali_fn, "r") as rf: + data = [l.strip().split(",") for l in rf.readlines()] + data = data[1:] + tmp = word_span_ind.split(",") + s, e = int(tmp[0]), int(tmp[-1]) + start = None + for j, item in enumerate(data): + if j == s and item[3] == "words": + if editType == 'insertion': + start = float(item[1]) + else: + start = float(item[0]) + if j == e and item[3] == "words": + if editType == 'insertion': + end = float(item[0]) + else: + end = float(item[1]) + assert start != None + break + return (start, end) + +if __name__ == "__main__": + def seed_everything(seed): + os.environ['PYTHONHASHSEED'] = str(seed) + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + formatter = ( + "%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d || %(message)s" + ) + logging.basicConfig(format=formatter, level=logging.INFO) + args = get_args() + # args.device = 'cpu' + args.allowed_repeat_tokens = eval(args.allowed_repeat_tokens) + seed_everything(args.seed) + + # load model + stime = time.time() + logging.info(f"loading model from {args.exp_dir}") + model, model_args, phn2num = get_model(args.exp_dir) + if not os.path.isfile(model_args.exp_dir): + model_args.exp_dir = args.exp_dir + logging.info(f"loading model done, took {time.time() - stime:.4f} sec") + + # setup text and audio tokenizer + text_tokenizer = TextTokenizer(backend="espeak") + audio_tokenizer = AudioTokenizer(signature=args.signature) # will also put the neural codec model on gpu + + with open(args.manifest_fn, "r") as rf: + manifest = [l.strip().split("\t") for l in rf.readlines()] + manifest = manifest[1:] + + # wav_fn txt_fn alingment_fn num_words word_span_ind + audio_fns = [] + target_texts = [] + mask_intervals = [] + edit_types = [] + new_spans = [] + orig_spans = [] + os.makedirs(args.output_dir, exist_ok=True) + if args.crop_concat: + mfa_temp = f"{args.output_dir}/mfa_temp" + os.makedirs(mfa_temp, exist_ok=True) + for item in manifest: + audio_fn = os.path.join(args.audio_root, item[0]) + temp = torchaudio.info(audio_fn) + audio_dur = temp.num_frames/temp.sample_rate + audio_fns.append(audio_fn) + target_text = item[2].split("|")[-1] + edit_types.append(item[5].split("|")) + new_spans.append(item[4].split("|")) + orig_spans.append(item[3].split("|")) + target_texts.append(target_text) # the last transcript is the target + # mi needs to be created from word_ind_span and alignment_fn, along with args.left_margin and args.right_margin + mis = [] + all_ind_intervals = item[3].split("|") + editTypes = item[5].split("|") + smaller_indx = [] + alignment_fn = os.path.join(args.audio_root, "aligned", item[0].replace(".wav", ".csv")) + if not os.path.isfile(alignment_fn): + alignment_fn = alignment_fn.replace("/aligned/", "/aligned_csv/") + assert os.path.isfile(alignment_fn), alignment_fn + for ind_inter,editType in zip(all_ind_intervals, editTypes): + # print(ind_inter) + mi = get_mask_interval(alignment_fn, ind_inter, editType) + mi = (max(mi[0] - args.left_margin, 1/args.codec_sr), min(mi[1] + args.right_margin, audio_dur)) # in seconds + mis.append(mi) + smaller_indx.append(mi[0]) + ind = np.argsort(smaller_indx) + mis = [mis[id] for id in ind] + mask_intervals.append(mis) + + + + for i, (audio_fn, target_text, mask_interval) in enumerate(tqdm.tqdm(zip(audio_fns, target_texts, mask_intervals))): + orig_mask_interval = mask_interval + mask_interval = [[round(cmi[0]*args.codec_sr), round(cmi[1]*args.codec_sr)] for cmi in mask_interval] + # logging.info(f"i: {i}, mask_interval: {mask_interval}") + mask_interval = torch.LongTensor(mask_interval) # [M,2] + orig_audio, new_audio = inference_one_sample(model, model_args, phn2num, text_tokenizer, audio_tokenizer, audio_fn, target_text, mask_interval, args.device, vars(args)) + + # save segments for comparison + orig_audio, new_audio = orig_audio[0].cpu(), new_audio[0].cpu() + # logging.info(f"length of the resynthesize orig audio: {orig_audio.shape}") + + save_fn_new = f"{args.output_dir}/{os.path.basename(audio_fn)[:-4]}_new_seed{args.seed}.wav" + + torchaudio.save(save_fn_new, new_audio, args.codec_audio_sr) + + save_fn_orig = f"{args.output_dir}/{os.path.basename(audio_fn)[:-4]}_orig.wav" + if not os.path.isfile(save_fn_orig): + orig_audio, orig_sr = torchaudio.load(audio_fn) + if orig_sr != args.codec_audio_sr: + orig_audio = torchaudio.transforms.Resample(orig_sr, args.codec_audio_sr)(orig_audio) + torchaudio.save(save_fn_orig, orig_audio, args.codec_audio_sr) + diff --git a/inference_tts.ipynb b/inference_tts.ipynb new file mode 100644 index 0000000..75c25a2 --- /dev/null +++ b/inference_tts.ipynb @@ -0,0 +1,182 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ[\"CUDA_DEVICE_ORDER\"]=\"PCI_BUS_ID\" \n", + "os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"0\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/pyp/miniconda3/envs/voicecraft/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + } + ], + "source": [ + "# import libs\n", + "import torch\n", + "import torchaudio\n", + "\n", + "from data.tokenizer import (\n", + " AudioTokenizer,\n", + " TextTokenizer,\n", + ")\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# hyperparameters for inference\n", + "left_margin = 0.08\n", + "right_margin = 0.08\n", + "seed = 1\n", + "codec_audio_sr = 16000\n", + "codec_sr = 50\n", + "top_k = 0\n", + "top_p = 0.8\n", + "temperature = 1\n", + "kvcache = 0\n", + "silence_tokens=[1388,1898,131]\n", + "# if there are long silence in the generated audio, reduce the stop_repetition to 3, 2 or even 1\n", + "stop_repetition = 2\n", + "# if there are long silence or unnaturally strecthed words, increase sample_batch_size to 2, 3 or even 4\n", + "# what this will do to the model is that the model will run sample_batch_size examples of the same audio, and pick the one that's the shortest\n", + "sample_batch_size = 1\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "\n", + "# point to the original file or record the file\n", + "# write down the transcript for the file, or run whisper to get the transcript (and you can modify it if it's not accurate), save it as a .txt file\n", + "orig_audio = \"/home/pyp/VoiceCraft/demo/84_121550_000074_000000.wav\"\n", + "orig_transcript = \"But when I had approached so near to them The common object, which the sense deceives, Lost not by distance any of its marks,\"\n", + "\n", + "# move the audio and transcript to temp folder\n", + "temp_folder = \"/home/pyp/VoiceCraft/demo/temp\"\n", + "os.makedirs(temp_folder, exist_ok=True)\n", + "os.system(f\"cp {orig_audio} {temp_folder}\")\n", + "filename = os.path.splitext(orig_audio.split(\"/\")[-1])[0]\n", + "with open(f\"{temp_folder}/{filename}.txt\", \"w\") as f:\n", + " f.write(orig_transcript)\n", + "# run MFA to get the alignment\n", + "align_temp = f\"{temp_folder}/mfa_alignments\"\n", + "os.makedirs(align_temp, exist_ok=True)\n", + "os.system(f\"mfa align -j 1 --output_format csv {temp_folder} english_us_arpa english_us_arpa {align_temp}\")\n", + "# if the above fails, it could be because the audio is too hard for the alignment model, increasing the beam size usually solves the issue\n", + "# os.system(f\"mfa align -j 1 --output_format csv {temp_folder} english_us_arpa english_us_arpa {align_temp} --beam 1000 --retry_beam 2000\")\n", + "audio_fn = f\"{temp_folder}/{filename}.wav\"\n", + "transcript_fn = f\"{temp_folder}/{filename}.txt\"\n", + "align_fn = f\"{align_temp}/{filename}.csv\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Dora directory: /tmp/audiocraft_pyp\n" + ] + } + ], + "source": [ + "# take a look at demo/temp/mfa_alignment, decide which part of the audio to use as prompt\n", + "cut_off_sec = 3.01 # according to forced-alignment file, the word \"common\" stop as 3.01 sec\n", + "target_transcript = \"But when I had approached so near to them The common I cannot believe that the same model can also do text to speech synthesis as well!\"\n", + "info = torchaudio.info(audio_fn)\n", + "audio_dur = info.num_frames / info.sample_rate\n", + "\n", + "assert cut_off_sec < audio_dur, f\"cut_off_sec {cut_off_sec} is larger than the audio duration {audio_dur}\"\n", + "prompt_end_frame = int(cut_off_sec * info.sample_rate)\n", + "\n", + "\n", + "# # load model, tokenizer, and other necessary files\n", + "from models import voicecraft\n", + "ckpt_fn = \"/data/scratch/pyp/exp_pyp/VoiceCraft/gigaspeech/pretrained_830M/best_bundle.pth\"\n", + "encodec_fn = \"/data/scratch/pyp/exp_pyp/audiocraft/encodec/xps/6f79c6a8/checkpoint.th\"\n", + "ckpt = torch.load(ckpt_fn, map_location=\"cpu\")\n", + "model = voicecraft.VoiceCraft(ckpt[\"config\"])\n", + "model.load_state_dict(ckpt[\"model\"])\n", + "model.to(device)\n", + "model.eval()\n", + "\n", + "phn2num = ckpt['phn2num']\n", + "\n", + "text_tokenizer = TextTokenizer(backend=\"espeak\")\n", + "audio_tokenizer = AudioTokenizer(signature=encodec_fn) # will also put the neural codec model on gpu\n", + "\n", + "# run the model to get the output\n", + "decode_config = {'top_k': top_k, 'top_p': top_p, 'temperature': temperature, 'stop_repetition': stop_repetition, 'kvcache': kvcache, \"codec_audio_sr\": codec_audio_sr, \"codec_sr\": codec_sr, \"silence_tokens\": silence_tokens, \"sample_batch_size\": sample_batch_size}\n", + "from inference_tts_scale import inference_one_sample\n", + "concated_audio, gen_audio = inference_one_sample(model, ckpt[\"config\"], phn2num, text_tokenizer, audio_tokenizer, audio_fn, target_transcript, device, decode_config, prompt_end_frame)\n", + " \n", + "# save segments for comparison\n", + "concated_audio, gen_audio = concated_audio[0].cpu(), gen_audio[0].cpu()\n", + "# logging.info(f\"length of the resynthesize orig audio: {orig_audio.shape}\")\n", + "\n", + "# output_dir\n", + "output_dir = \"/home/pyp/VoiceCraft/demo/generated_tts\"\n", + "os.makedirs(output_dir, exist_ok=True)\n", + "\n", + "seg_save_fn_gen = f\"{output_dir}/{os.path.basename(audio_fn)[:-4]}_gen_seed{seed}.wav\"\n", + "seg_save_fn_concat = f\"{output_dir}/{os.path.basename(audio_fn)[:-4]}_concat_seed{seed}.wav\" \n", + "\n", + "torchaudio.save(seg_save_fn_gen, gen_audio, codec_audio_sr)\n", + "torchaudio.save(seg_save_fn_concat, concated_audio, codec_audio_sr)\n", + "\n", + "# if you get error importing T5 in transformers\n", + "# try \n", + "# pip uninstall Pillow\n", + "# pip install Pillow\n", + "# you are might get warnings like WARNING:phonemizer:words count mismatch on 300.0% of the lines (3/1), this can be safely ignored" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "voicecraft", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/inference_tts_scale.py b/inference_tts_scale.py new file mode 100644 index 0000000..2ebb78c --- /dev/null +++ b/inference_tts_scale.py @@ -0,0 +1,190 @@ +import argparse, pickle +import logging +import os, random +import numpy as np +import torch +import torchaudio + +from data.tokenizer import ( + AudioTokenizer, + TextTokenizer, + tokenize_audio, + tokenize_text +) + +from models import voicecraft +import argparse, time, tqdm + + +# this script only works for the musicgen architecture +def get_args(): + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--manifest_fn", type=str, default="path/to/eval_metadata_file") + parser.add_argument("--audio_root", type=str, default="path/to/audio_folder") + parser.add_argument("--exp_dir", type=str, default="path/to/model_folder") + parser.add_argument("--seed", type=int, default=1) + parser.add_argument("--codec_audio_sr", type=int, default=16000, help='the sample rate of audio that the codec is trained for') + parser.add_argument("--codec_sr", type=int, default=50, help='the sample rate of the codec codes') + parser.add_argument("--top_k", type=int, default=0, help="sampling param") + parser.add_argument("--top_p", type=float, default=0.8, help="sampling param") + parser.add_argument("--temperature", type=float, default=1.0, help="sampling param") + parser.add_argument("--output_dir", type=str, default=None) + parser.add_argument("--device", type=str, default="cuda") + parser.add_argument("--signature", type=str, default=None, help="path to the encodec model") + parser.add_argument("--crop_concat", type=int, default=0) + parser.add_argument("--stop_repetition", type=int, default=-1, help="used for inference, when the number of consecutive repetition of a token is bigger than this, stop it") + parser.add_argument("--kvcache", type=int, default=1, help='if true, use kv cache, which is 4-8x faster than without') + parser.add_argument("--sample_batch_size", type=int, default=1, help="batch size for sampling, NOTE that it's not running inference for several samples, but duplicate one input sample batch_size times, and during inference, we only return the shortest generation") + parser.add_argument("--silence_tokens", type=str, default="[1388,1898,131]", help="note that if you are not using the pretrained encodec 6f79c6a8, make sure you specified it yourself, rather than using the default") + return parser.parse_args() + + +@torch.no_grad() +def inference_one_sample(model, model_args, phn2num, text_tokenizer, audio_tokenizer, audio_fn, target_text, device, decode_config, prompt_end_frame): + # phonemize + text_tokens = [phn2num[phn] for phn in + tokenize_text( + text_tokenizer, text=target_text.strip() + ) if phn in phn2num + ] + text_tokens = torch.LongTensor(text_tokens).unsqueeze(0) + text_tokens_lens = torch.LongTensor([text_tokens.shape[-1]]) + + # encode audio + encoded_frames = tokenize_audio(audio_tokenizer, audio_fn, offset=0, num_frames=prompt_end_frame) + original_audio = encoded_frames[0][0].transpose(2,1) # [1,T,K] + assert original_audio.ndim==3 and original_audio.shape[0] == 1 and original_audio.shape[2] == model_args.n_codebooks, original_audio.shape + logging.info(f"original audio length: {original_audio.shape[1]} codec frames, which is {original_audio.shape[1]/decode_config['codec_sr']:.2f} sec.") + + # forward + stime = time.time() + if decode_config['sample_batch_size'] <= 1: + logging.info(f"running inference with batch size 1") + concat_frames, gen_frames = model.inference_tts( + text_tokens.to(device), + text_tokens_lens.to(device), + original_audio[...,:model_args.n_codebooks].to(device), # [1,T,8] + top_k=decode_config['top_k'], + top_p=decode_config['top_p'], + temperature=decode_config['temperature'], + stop_repetition=decode_config['stop_repetition'], + kvcache=decode_config['kvcache'], + silence_tokens=eval(decode_config['silence_tokens']) if type(decode_config['silence_tokens'])==str else decode_config['silence_tokens'] + ) # output is [1,K,T] + else: + logging.info(f"running inference with batch size {decode_config['sample_batch_size']}, i.e. return the shortest among {decode_config['sample_batch_size']} generations.") + concat_frames, gen_frames = model.inference_tts_batch( + text_tokens.to(device), + text_tokens_lens.to(device), + original_audio[...,:model_args.n_codebooks].to(device), # [1,T,8] + top_k=decode_config['top_k'], + top_p=decode_config['top_p'], + temperature=decode_config['temperature'], + stop_repetition=decode_config['stop_repetition'], + kvcache=decode_config['kvcache'], + batch_size = decode_config['sample_batch_size'], + silence_tokens=eval(decode_config['silence_tokens']) if type(decode_config['silence_tokens'])==str else decode_config['silence_tokens'] + ) # output is [1,K,T] + logging.info(f"inference on one sample take: {time.time() - stime:.4f} sec.") + + logging.info(f"generated encoded_frames.shape: {gen_frames.shape}, which is {gen_frames.shape[-1]/decode_config['codec_sr']} sec.") + + # for timestamp, codes in enumerate(gen_frames[0].transpose(1,0)): + # logging.info(f"{timestamp}: {codes.tolist()}") + # decode (both original and generated) + concat_sample = audio_tokenizer.decode( + [(concat_frames, None)] # [1,T,8] -> [1,8,T] + ) + gen_sample = audio_tokenizer.decode( + [(gen_frames, None)] + ) + + # return + return concat_sample, gen_sample + +def get_model(exp_dir, device=None): + with open(os.path.join(exp_dir, "args.pkl"), "rb") as f: + model_args = pickle.load(f) + + logging.info("load model weights...") + model = voicecraft.VoiceCraft(model_args) + ckpt_fn = os.path.join(exp_dir, "best_bundle.pth") + ckpt = torch.load(ckpt_fn, map_location='cpu')['model'] + phn2num = torch.load(ckpt_fn, map_location='cpu')['phn2num'] + model.load_state_dict(ckpt) + del ckpt + logging.info("done loading weights...") + if device == None: + device = torch.device("cpu") + if torch.cuda.is_available(): + device = torch.device("cuda:0") + model.to(device) + model.eval() + return model, model_args, phn2num + +if __name__ == "__main__": + def seed_everything(seed): + os.environ['PYTHONHASHSEED'] = str(seed) + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + formatter = ( + "%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d || %(message)s" + ) + logging.basicConfig(format=formatter, level=logging.INFO) + args = get_args() + # args.device='cpu' + seed_everything(args.seed) + + os.makedirs(args.output_dir, exist_ok=True) + # load model + + with open(args.manifest_fn, "r") as rf: + manifest = [l.strip().split("\t") for l in rf.readlines()] + manifest = manifest[1:] + manifest = [[item[0], item[2], item[3], item[1], item[5]] for item in manifest] + + stime = time.time() + logging.info(f"loading model from {args.exp_dir}") + model, model_args, phn2num = get_model(args.exp_dir) + logging.info(f"loading model done, took {time.time() - stime:.4f} sec") + + # setup text and audio tokenizer + text_tokenizer = TextTokenizer(backend="espeak") + audio_tokenizer = AudioTokenizer(signature=args.signature) # will also put the neural codec model on gpu + + audio_fns = [] + texts = [] + prompt_end_frames = [] + new_audio_fns = [] + text_to_syn = [] + + for item in manifest: + audio_fn = os.path.join(args.audio_root, item[0]) + audio_fns.append(audio_fn) + temp = torchaudio.info(audio_fn) + prompt_end_frames.append(round(float(item[2])*temp.sample_rate)) + texts.append(item[1]) + new_audio_fns.append(item[-2]) + all_text = item[1].split(" ") + start_ind = int(item[-1].split(",")[0]) + text_to_syn.append(" ".join(all_text[start_ind:])) + + for i, (audio_fn, text, prompt_end_frame, new_audio_fn, to_syn) in enumerate(tqdm.tqdm((zip(audio_fns, texts, prompt_end_frames, new_audio_fns, text_to_syn)))): + output_expected_sr = args.codec_audio_sr + concated_audio, gen_audio = inference_one_sample(model, model_args, phn2num, text_tokenizer, audio_tokenizer, audio_fn, text, args.device, vars(args), prompt_end_frame) + + # save segments for comparison + concated_audio, gen_audio = concated_audio[0].cpu(), gen_audio[0].cpu() + if output_expected_sr != args.codec_audio_sr: + gen_audio = torchaudio.transforms.Resample(output_expected_sr, args.codec_audio_sr)(gen_audio) + concated_audio = torchaudio.transforms.Resample(output_expected_sr, args.codec_audio_sr)(concated_audio) + + seg_save_fn_gen = f"{args.output_dir}/gen_{new_audio_fn[:-4]}_{i}_seed{args.seed}.wav" + seg_save_fn_concat = f"{args.output_dir}/concat_{new_audio_fn[:-4]}_{i}_seed{args.seed}.wav" + + torchaudio.save(seg_save_fn_gen, gen_audio, args.codec_audio_sr) + torchaudio.save(seg_save_fn_concat, concated_audio, args.codec_audio_sr) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..0d3fac5 --- /dev/null +++ b/main.py @@ -0,0 +1,45 @@ +from pathlib import Path +import torch +import pickle +import argparse +import logging +import torch.distributed as dist +from config import MyParser +from steps import trainer + + +if __name__ == "__main__": + formatter = ( + "%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d || %(message)s" + ) + logging.basicConfig(format=formatter, level=logging.INFO) + + torch.cuda.empty_cache() + args = MyParser().parse_args() + logging.info(args) + exp_dir = Path(args.exp_dir) + exp_dir.mkdir(exist_ok=True, parents=True) + logging.info(f"exp_dir: {str(exp_dir)}") + + if args.resume: + resume = args.resume + assert(bool(args.exp_dir)) + with open("%s/args.pkl" % args.exp_dir, "rb") as f: + old_args = pickle.load(f) + new_args = vars(args) + old_args = vars(old_args) + for key in new_args: + if key not in old_args or old_args[key] != new_args[key]: + old_args[key] = new_args[key] + args = argparse.Namespace(**old_args) + args.resume = resume + else: + with open("%s/args.pkl" % args.exp_dir, "wb") as f: + pickle.dump(args, f) + + dist.init_process_group(backend='nccl', init_method='env://') + rank = dist.get_rank() + world_size = dist.get_world_size() + torch.cuda.set_device(rank) + my_trainer = trainer.Trainer(args, world_size, rank) + my_trainer.train() \ No newline at end of file diff --git a/models/codebooks_patterns.py b/models/codebooks_patterns.py new file mode 100644 index 0000000..24c6319 --- /dev/null +++ b/models/codebooks_patterns.py @@ -0,0 +1,538 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from collections import namedtuple +from dataclasses import dataclass +from functools import lru_cache +import logging +import typing as tp + +from abc import ABC, abstractmethod +import torch + +LayoutCoord = namedtuple('LayoutCoord', ['t', 'q']) # (timestep, codebook index) +PatternLayout = tp.List[tp.List[LayoutCoord]] # Sequence of coordinates + + +@dataclass +class Pattern: + """Base implementation of a pattern over a sequence with multiple codebooks. + + The codebook pattern consists in a layout, defining for each sequence step + the list of coordinates of each codebook timestep in the resulting interleaved sequence. + The first item of the pattern is always an empty list in order to properly insert a special token + to start with. For convenience, we also keep track of ``n_q`` the number of codebooks used for the pattern + and ``timesteps`` the number of timesteps corresponding to the original sequence. + + The pattern provides convenient methods to build and revert interleaved sequences from it: + ``build_pattern_sequence`` maps a given a dense input tensor of multi-codebook sequence from [B, K, T] + to the interleaved sequence of shape [B, K, S] applying the pattern, with S being the batch size, + K being the number of codebooks, T the number of original timesteps and S the number of sequence steps + for the output sequence. The unfilled positions are replaced with a special token and the built sequence + is returned along with a mask indicating valid tokens. + ``revert_pattern_sequence`` maps back an interleaved sequence of shape [B, K, S] to the original alignment + of codebooks across timesteps to an output tensor of shape [B, K, T], using again a special token and a mask + to fill and specify invalid positions if needed. + See the dedicated methods for more details. + """ + # Pattern layout, for each sequence step, we have a list of coordinates + # corresponding to the original codebook timestep and position. + # The first list is always an empty list in order to properly insert + # a special token to start with. + layout: PatternLayout + timesteps: int + n_q: int + + def __post_init__(self): + assert len(self.layout) > 0 + assert self.layout[0] == [] + self._validate_layout() + self._build_reverted_sequence_scatter_indexes = lru_cache(100)(self._build_reverted_sequence_scatter_indexes) + self._build_pattern_sequence_scatter_indexes = lru_cache(100)(self._build_pattern_sequence_scatter_indexes) + # logging.info("New pattern, time steps: %d, sequence steps: %d", self.timesteps, len(self.layout)) + + def _validate_layout(self): + """Runs checks on the layout to ensure a valid pattern is defined. + A pattern is considered invalid if: + - Multiple timesteps for a same codebook are defined in the same sequence step + - The timesteps for a given codebook are not in ascending order as we advance in the sequence + (this would mean that we have future timesteps before past timesteps). + """ + q_timesteps = {q: 0 for q in range(self.n_q)} + for s, seq_coords in enumerate(self.layout): + if len(seq_coords) > 0: + qs = set() + for coord in seq_coords: + qs.add(coord.q) + last_q_timestep = q_timesteps[coord.q] + assert coord.t >= last_q_timestep, \ + f"Past timesteps are found in the sequence for codebook = {coord.q} at step {s}" + q_timesteps[coord.q] = coord.t + # each sequence step contains at max 1 coordinate per codebook + assert len(qs) == len(seq_coords), \ + f"Multiple entries for a same codebook are found at step {s}" + + @property + def num_sequence_steps(self): + return len(self.layout) - 1 + + @property + def max_delay(self): + max_t_in_seq_coords = 0 + for seq_coords in self.layout[1:]: + for coords in seq_coords: + max_t_in_seq_coords = max(max_t_in_seq_coords, coords.t + 1) + return max_t_in_seq_coords - self.timesteps + + @property + def valid_layout(self): + valid_step = len(self.layout) - self.max_delay + return self.layout[:valid_step] + + def get_sequence_coords_with_timestep(self, t: int, q: tp.Optional[int] = None): + """Get codebook coordinates in the layout that corresponds to the specified timestep t + and optionally to the codebook q. Coordinates are returned as a tuple with the sequence step + and the actual codebook coordinates. + """ + assert t <= self.timesteps, "provided timesteps is greater than the pattern's number of timesteps" + if q is not None: + assert q <= self.n_q, "provided number of codebooks is greater than the pattern's number of codebooks" + coords = [] + for s, seq_codes in enumerate(self.layout): + for code in seq_codes: + if code.t == t and (q is None or code.q == q): + coords.append((s, code)) + return coords + + def get_steps_with_timestep(self, t: int, q: tp.Optional[int] = None) -> tp.List[int]: + return [step for step, coords in self.get_sequence_coords_with_timestep(t, q)] + + def get_first_step_with_timesteps(self, t: int, q: tp.Optional[int] = None) -> tp.Optional[int]: + steps_with_timesteps = self.get_steps_with_timestep(t, q) + return steps_with_timesteps[0] if len(steps_with_timesteps) > 0 else None + + def _build_pattern_sequence_scatter_indexes(self, timesteps: int, n_q: int, keep_only_valid_steps: bool, + device: tp.Union[torch.device, str] = 'cpu'): + """Build scatter indexes corresponding to the pattern, up to the provided sequence_steps. + + Args: + timesteps (int): Maximum number of timesteps steps to consider. + keep_only_valid_steps (bool): Restrict the pattern layout to match only valid steps. + device (Union[torch.device, str]): Device for created tensors. + Returns: + indexes (torch.Tensor): Indexes corresponding to the sequence, of shape [K, S]. + mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes, of shape [K, S]. + """ + assert n_q == self.n_q, f"invalid number of codebooks for the sequence and the pattern: {n_q} != {self.n_q}" + assert timesteps <= self.timesteps, "invalid number of timesteps used to build the sequence from the pattern" + # use the proper layout based on whether we limit ourselves to valid steps only or not, + # note that using the valid_layout will result in a truncated sequence up to the valid steps + ref_layout = self.valid_layout if keep_only_valid_steps else self.layout + # single item indexing being super slow with pytorch vs. numpy, so we use numpy here + indexes = torch.zeros(n_q, len(ref_layout), dtype=torch.long).numpy() + mask = torch.zeros(n_q, len(ref_layout), dtype=torch.bool).numpy() + # fill indexes with last sequence step value that will correspond to our special token + # the last value is n_q * timesteps as we have flattened z and append special token as the last token + # which will correspond to the index: n_q * timesteps + indexes[:] = n_q * timesteps + # iterate over the pattern and fill scattered indexes and mask + for s, sequence_coords in enumerate(ref_layout): + for coords in sequence_coords: + if coords.t < timesteps: + indexes[coords.q, s] = coords.t + coords.q * timesteps + mask[coords.q, s] = 1 + indexes = torch.from_numpy(indexes).to(device) + mask = torch.from_numpy(mask).to(device) + return indexes, mask + + def build_pattern_sequence(self, z: torch.Tensor, special_token: int, keep_only_valid_steps: bool = False): + """Build sequence corresponding to the pattern from the input tensor z. + The sequence is built using up to sequence_steps if specified, and non-pattern + coordinates are filled with the special token. + + Args: + z (torch.Tensor): Input tensor of multi-codebooks sequence, of shape [B, K, T]. + special_token (int): Special token used to fill non-pattern coordinates in the new sequence. + keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps. + Steps that are beyond valid steps will be replaced by the special_token in that case. + Returns: + values (torch.Tensor): Interleaved sequence matching the pattern, of shape [B, K, S] with S + corresponding either to the sequence_steps if provided, otherwise to the length of the pattern. + indexes (torch.Tensor): Indexes corresponding to the interleaved sequence, of shape [K, S]. + mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, S]. + """ + B, K, T = z.shape + indexes, mask = self._build_pattern_sequence_scatter_indexes( + T, K, keep_only_valid_steps=keep_only_valid_steps, device=str(z.device) + ) + z = z.view(B, -1) + # we append the special token as the last index of our flattened z tensor + z = torch.cat([z, torch.zeros_like(z[:, :1]) + special_token], dim=1) + values = z[:, indexes.view(-1)] + values = values.view(B, K, indexes.shape[-1]) + return values, indexes, mask + + def _build_reverted_sequence_scatter_indexes(self, sequence_steps: int, n_q: int, + keep_only_valid_steps: bool = False, + is_model_output: bool = False, + device: tp.Union[torch.device, str] = 'cpu'): + """Builds scatter indexes required to retrieve the original multi-codebook sequence + from interleaving pattern. + + Args: + sequence_steps (int): Sequence steps. + n_q (int): Number of codebooks. + keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps. + Steps that are beyond valid steps will be replaced by the special_token in that case. + is_model_output (bool): Whether to keep the sequence item corresponding to initial special token or not. + device (Union[torch.device, str]): Device for created tensors. + Returns: + torch.Tensor: Indexes for reconstructing the output, of shape [K, T]. + mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, T]. + """ + ref_layout = self.valid_layout if keep_only_valid_steps else self.layout + # TODO(jade): Do we want to further truncate to only valid timesteps here as well? + timesteps = self.timesteps + assert n_q == self.n_q, f"invalid number of codebooks for the sequence and the pattern: {n_q} != {self.n_q}" + assert sequence_steps <= len(ref_layout), \ + f"sequence to revert is longer than the defined pattern: {sequence_steps} > {len(ref_layout)}" + + # ensure we take the appropriate indexes to keep the model output from the first special token as well + if is_model_output: + ref_layout = ref_layout[1:] + + # single item indexing being super slow with pytorch vs. numpy, so we use numpy here + indexes = torch.zeros(n_q, timesteps, dtype=torch.long).numpy() + mask = torch.zeros(n_q, timesteps, dtype=torch.bool).numpy() + # fill indexes with last sequence step value that will correspond to our special token + indexes[:] = n_q * sequence_steps + for s, sequence_codes in enumerate(ref_layout): + if s < sequence_steps: + for code in sequence_codes: + if code.t < timesteps: + indexes[code.q, code.t] = s + code.q * sequence_steps + mask[code.q, code.t] = 1 + indexes = torch.from_numpy(indexes).to(device) + mask = torch.from_numpy(mask).to(device) + return indexes, mask + + def revert_pattern_sequence(self, s: torch.Tensor, special_token: int, keep_only_valid_steps: bool = False): + """Revert a sequence built from the pattern back to the original multi-codebook sequence without interleaving. + The sequence is reverted using up to timesteps if specified, and non-pattern coordinates + are filled with the special token. + + Args: + s (torch.Tensor): Interleaved sequence tensor obtained from the pattern, of shape [B, K, S]. + special_token (int or float): Special token used to fill non-pattern coordinates in the new sequence. + Returns: + values (torch.Tensor): Interleaved sequence matching the pattern, of shape [B, K, T] with T + corresponding either to the timesteps if provided, or the total timesteps in pattern otherwise. + indexes (torch.Tensor): Indexes corresponding to the interleaved sequence, of shape [K, T]. + mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, T]. + """ + B, K, S = s.shape + indexes, mask = self._build_reverted_sequence_scatter_indexes( + S, K, keep_only_valid_steps, is_model_output=False, device=str(s.device) + ) + s = s.view(B, -1) + # we append the special token as the last index of our flattened z tensor + s = torch.cat([s, torch.zeros_like(s[:, :1]) + special_token], dim=1) + values = s[:, indexes.view(-1)] + values = values.view(B, K, indexes.shape[-1]) + return values, indexes, mask + + def revert_pattern_logits(self, logits: torch.Tensor, special_token: float, keep_only_valid_steps: bool = False): + """Revert model logits obtained on a sequence built from the pattern + back to a tensor matching the original sequence. + + This method is similar to ``revert_pattern_sequence`` with the following specificities: + 1. It is designed to work with the extra cardinality dimension + 2. We return the logits for the first sequence item that matches the special_token and + which matching target in the original sequence is the first item of the sequence, + while we skip the last logits as there is no matching target + """ + B, card, K, S = logits.shape + indexes, mask = self._build_reverted_sequence_scatter_indexes( + S, K, keep_only_valid_steps, is_model_output=True, device=logits.device + ) + logits = logits.reshape(B, card, -1) + # we append the special token as the last index of our flattened z tensor + logits = torch.cat([logits, torch.zeros_like(logits[:, :, :1]) + special_token], dim=-1) # [B, card, K x S] + values = logits[:, :, indexes.view(-1)] + values = values.view(B, card, K, indexes.shape[-1]) + return values, indexes, mask + + +class CodebooksPatternProvider(ABC): + """Abstraction around providing pattern for interleaving codebooks. + + The CodebooksPatternProvider abstraction allows to implement various strategies to + define interleaving pattern of sequences composed of multiple codebooks. For a given + number of codebooks `n_q`, the pattern provider can generate a specified pattern + corresponding to a sequence of `T` timesteps with `n_q` parallel codebooks. This pattern + can be used to construct a new sequence from the original codes respecting the specified + pattern. The pattern is defined as a list of list of code coordinates, code coordinate + being a tuple with the original timestep and codebook to build the new sequence. + Note that all patterns must start with an empty list that is then used to insert a first + sequence step of special tokens in the newly generated sequence. + + Args: + n_q (int): number of codebooks. + cached (bool): if True, patterns for a given length are cached. In general + that should be true for efficiency reason to avoid synchronization points. + """ + def __init__(self, n_q: int, cached: bool = True): + assert n_q > 0 + self.n_q = n_q + self.get_pattern = lru_cache(100)(self.get_pattern) # type: ignore + + @abstractmethod + def get_pattern(self, timesteps: int) -> Pattern: + """Builds pattern with specific interleaving between codebooks. + + Args: + timesteps (int): Total numer of timesteps. + """ + raise NotImplementedError() + + +class DelayedPatternProvider(CodebooksPatternProvider): + """Provider for delayed pattern across delayed codebooks. + Codebooks are delayed in the sequence and sequence steps will contain codebooks + from different timesteps. + + Example: + Taking timesteps=4 and n_q=3, delays=None, the multi-codebook sequence: + [[1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4]] + The resulting sequence obtained from the returned pattern is: + [[S, 1, 2, 3, 4], + [S, S, 1, 2, 3], + [S, S, S, 1, 2]] + (with S being a special token) + + Args: + n_q (int): Number of codebooks. + delays (Optional[List[int]]): Delay for each of the codebooks. + If delays not defined, each codebook is delayed by 1 compared to the previous one. + flatten_first (int): Flatten the first N timesteps. + empty_initial (int): Prepend with N empty list of coordinates. + """ + def __init__(self, n_q: int, delays: tp.Optional[tp.List[int]] = None, + flatten_first: int = 0, empty_initial: int = 0): + super().__init__(n_q) + if delays is None: + delays = list(range(n_q)) + self.delays = delays + self.flatten_first = flatten_first + self.empty_initial = empty_initial + assert len(self.delays) == self.n_q + assert sorted(self.delays) == self.delays + + def get_pattern(self, timesteps: int) -> Pattern: + out: PatternLayout = [[]] + max_delay = max(self.delays) + if self.empty_initial: + out += [[] for _ in range(self.empty_initial)] + if self.flatten_first: + for t in range(min(timesteps, self.flatten_first)): + for q in range(self.n_q): + out.append([LayoutCoord(t, q)]) + for t in range(self.flatten_first, timesteps + max_delay): + v = [] + for q, delay in enumerate(self.delays): + t_for_q = t - delay + if t_for_q >= self.flatten_first: + v.append(LayoutCoord(t_for_q, q)) + out.append(v) + return Pattern(out, n_q=self.n_q, timesteps=timesteps) + + +class ParallelPatternProvider(DelayedPatternProvider): + """Provider for parallel pattern across codebooks. + This pattern provider is a special case of the delayed pattern with actually no delay, + hence delays=repeat(0, n_q). + + Args: + n_q (int): Number of codebooks. + """ + def __init__(self, n_q: int): + super().__init__(n_q, [0] * n_q) + + +class UnrolledPatternProvider(CodebooksPatternProvider): + """Provider for unrolling codebooks pattern. + This pattern provider enables to represent the codebook flattened completely or only to some extend + while also specifying a given delay between the flattened codebooks representation, allowing to + unroll the codebooks in the sequence. + + Example: + 1. Flattening of the codebooks. + By default, the pattern provider will fully flatten the codebooks such as flattening=range(n_q), + taking n_q = 3 and timesteps = 4: + [[1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4]] + will result into: + [[S, S, 1, S, S, 2, S, S, 3, S, S, 4], + [S, 1, S, S, 2, S, S, 3, S, S, 4, S], + [1, S, S, 2, S, S, 3, S, S, 4, S, S]] + 2. Partial flattening of the codebooks. The ``flattening`` parameter allows to specify the inner step + for each of the codebook, allowing to define which codebook to flatten (or keep in parallel), for example + taking n_q = 3, timesteps = 4 and flattening = [0, 1, 1]: + [[1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4]] + will result into: + [[S, 1, S, S, 2, S, S, 3, S, S, 4, S], + [S, 1, S, S, 2, S, S, 3, S, S, 4, S], + [1, S, S, 2, S, S, 3, S, S, 4, S, S]] + 3. Flattening with delay. The ``delay`` parameter allows to further unroll the sequence of codebooks + allowing to specify the delay per codebook. Note that the delay between codebooks flattened to the + same inner timestep should be coherent. For example, taking n_q = 3, timesteps = 4, flattening = [0, 1, 1] + and delays = [0, 3, 3]: + [[1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4]] + will result into: + [[S, S, S, 1, S, 2, S, 3, S, 4], + [S, S, S, 1, S, 2, S, 3, S, 4], + [1, 2, 3, S, 4, S, 5, S, 6, S]] + + Args: + n_q (int): Number of codebooks. + flattening (Optional[List[int]]): Flattening schema over the codebooks. If not defined, + the codebooks will be flattened to 1 codebook per step, meaning that the sequence will + have n_q extra steps for each timestep. + delays (Optional[List[int]]): Delay for each of the codebooks. If not defined, + no delay is added and therefore will default to [0] * ``n_q``. + Note that two codebooks that will be flattened to the same inner step + should have the same delay, otherwise the pattern is considered as invalid. + """ + FlattenedCodebook = namedtuple('FlattenedCodebook', ['codebooks', 'delay']) + + def __init__(self, n_q: int, flattening: tp.Optional[tp.List[int]] = None, + delays: tp.Optional[tp.List[int]] = None): + super().__init__(n_q) + if flattening is None: + flattening = list(range(n_q)) + if delays is None: + delays = [0] * n_q + assert len(flattening) == n_q + assert len(delays) == n_q + assert sorted(flattening) == flattening + assert sorted(delays) == delays + self._flattened_codebooks = self._build_flattened_codebooks(delays, flattening) + self.max_delay = max(delays) + + def _build_flattened_codebooks(self, delays: tp.List[int], flattening: tp.List[int]): + """Build a flattened codebooks representation as a dictionary of inner step + and the actual codebook indices corresponding to the flattened codebook. For convenience, we + also store the delay associated to the flattened codebook to avoid maintaining an extra mapping. + """ + flattened_codebooks: dict = {} + for q, (inner_step, delay) in enumerate(zip(flattening, delays)): + if inner_step not in flattened_codebooks: + flat_codebook = UnrolledPatternProvider.FlattenedCodebook(codebooks=[q], delay=delay) + else: + flat_codebook = flattened_codebooks[inner_step] + assert flat_codebook.delay == delay, ( + "Delay and flattening between codebooks is inconsistent: ", + "two codebooks flattened to the same position should have the same delay." + ) + flat_codebook.codebooks.append(q) + flattened_codebooks[inner_step] = flat_codebook + return flattened_codebooks + + @property + def _num_inner_steps(self): + """Number of inner steps to unroll between timesteps in order to flatten the codebooks. + """ + return max([inner_step for inner_step in self._flattened_codebooks.keys()]) + 1 + + def num_virtual_steps(self, timesteps: int) -> int: + return timesteps * self._num_inner_steps + 1 + + def get_pattern(self, timesteps: int) -> Pattern: + """Builds pattern for delay across codebooks. + + Args: + timesteps (int): Total numer of timesteps. + """ + # the PatternLayout is built as a tuple of sequence position and list of coordinates + # so that it can be reordered properly given the required delay between codebooks of given timesteps + indexed_out: list = [(-1, [])] + max_timesteps = timesteps + self.max_delay + for t in range(max_timesteps): + # for each timestep, we unroll the flattened codebooks, + # emitting the sequence step with the corresponding delay + for step in range(self._num_inner_steps): + if step in self._flattened_codebooks: + # we have codebooks at this virtual step to emit + step_codebooks = self._flattened_codebooks[step] + t_for_q = t + step_codebooks.delay + coords = [LayoutCoord(t, q) for q in step_codebooks.codebooks] + if t_for_q < max_timesteps and t < max_timesteps: + indexed_out.append((t_for_q, coords)) + else: + # there is no codebook in this virtual step so we emit an empty list + indexed_out.append((t, [])) + out = [coords for _, coords in sorted(indexed_out)] + return Pattern(out, n_q=self.n_q, timesteps=timesteps) + + +class VALLEPattern(CodebooksPatternProvider): + """Almost VALL-E style pattern. We futher allow some delays for the + codebooks other than the first one. + + Args: + n_q (int): Number of codebooks. + delays (Optional[List[int]]): Delay for each of the codebooks. + If delays not defined, each codebook is delayed by 1 compared to the previous one. + """ + def __init__(self, n_q: int, delays: tp.Optional[tp.List[int]] = None): + super().__init__(n_q) + if delays is None: + delays = [0] * (n_q - 1) + self.delays = delays + assert len(self.delays) == self.n_q - 1 + assert sorted(self.delays) == self.delays + + def get_pattern(self, timesteps: int) -> Pattern: + out: PatternLayout = [[]] + for t in range(timesteps): + out.append([LayoutCoord(t, 0)]) + max_delay = max(self.delays) + for t in range(timesteps + max_delay): + v = [] + for q, delay in enumerate(self.delays): + t_for_q = t - delay + if t_for_q >= 0: + v.append(LayoutCoord(t_for_q, q + 1)) + out.append(v) + return Pattern(out, n_q=self.n_q, timesteps=timesteps) + + +class MusicLMPattern(CodebooksPatternProvider): + """Almost MusicLM style pattern. This is equivalent to full flattening + but in a different order. + + Args: + n_q (int): Number of codebooks. + group_by (int): Number of codebooks to group together. + """ + def __init__(self, n_q: int, group_by: int = 2): + super().__init__(n_q) + self.group_by = group_by + + def get_pattern(self, timesteps: int) -> Pattern: + out: PatternLayout = [[]] + for offset in range(0, self.n_q, self.group_by): + for t in range(timesteps): + for q in range(offset, offset + self.group_by): + out.append([LayoutCoord(t, q)]) + return Pattern(out, n_q=self.n_q, timesteps=timesteps) \ No newline at end of file diff --git a/models/modules/__init__.py b/models/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/modules/activation.py b/models/modules/activation.py new file mode 100644 index 0000000..cea9b01 --- /dev/null +++ b/models/modules/activation.py @@ -0,0 +1,653 @@ +# cp from https://github.com/lifeiteng/vall-e/blob/main/valle/modules/activation.py, modified by Puyuan Peng, 2024 +from typing import Optional, Tuple + +import torch +from torch import Tensor +from torch.nn import Linear, Module +from torch.nn import functional as F +from torch.nn.init import constant_, xavier_normal_, xavier_uniform_ +from torch.nn.modules.linear import NonDynamicallyQuantizableLinear +from torch.nn.parameter import Parameter +import logging +from typing import Callable, List, Optional, Tuple, Union +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from torch.types import _dtype as DType +else: + # The JIT doesn't understand Union, nor torch.dtype here + DType = int + +def _canonical_mask( + mask: Optional[Tensor], + mask_name: str, + other_type: Optional[DType], + other_name: str, + target_type: DType, + check_other: bool = True, +) -> Optional[Tensor]: + + if mask is not None: + _mask_dtype = mask.dtype + _mask_is_float = torch.is_floating_point(mask) + if _mask_dtype != torch.bool and not _mask_is_float: + raise AssertionError( + f"only bool and floating types of {mask_name} are supported") + if check_other and other_type is not None: + if _mask_dtype != other_type: + warnings.warn( + f"Support for mismatched {mask_name} and {other_name} " + "is deprecated. Use same type for both instead." + ) + if not _mask_is_float: + mask = ( + torch.zeros_like(mask, dtype=target_type) + .masked_fill_(mask, float("-inf")) + ) + return mask + +def _in_projection_packed( + q: Tensor, + k: Tensor, + v: Tensor, + w: Tensor, + b: Optional[Tensor] = None, +) -> List[Tensor]: + r""" + Performs the in-projection step of the attention operation, using packed weights. + Output is a triple containing projection tensors for query, key and value. + + Args: + q, k, v: query, key and value tensors to be projected. For self-attention, + these are typically the same tensor; for encoder-decoder attention, + k and v are typically the same tensor. (We take advantage of these + identities for performance if they are present.) Regardless, q, k and v + must share a common embedding dimension; otherwise their shapes may vary. + w: projection weights for q, k and v, packed into a single tensor. Weights + are packed along dimension 0, in q, k, v order. + b: optional projection biases for q, k and v, packed into a single tensor + in q, k, v order. + + Shape: + Inputs: + - q: :math:`(..., E)` where E is the embedding dimension + - k: :math:`(..., E)` where E is the embedding dimension + - v: :math:`(..., E)` where E is the embedding dimension + - w: :math:`(E * 3, E)` where E is the embedding dimension + - b: :math:`E * 3` where E is the embedding dimension + + Output: + - in output list :math:`[q', k', v']`, each output tensor will have the + same shape as the corresponding input tensor. + """ + E = q.size(-1) + if k is v: + if q is k: + # self-attention + proj = F.linear(q, w, b) + # reshape to 3, E and not E, 3 is deliberate for better memory coalescing and keeping same order as chunk() + proj = proj.unflatten(-1, (3, E)).unsqueeze(0).transpose(0, -2).squeeze(-2).contiguous() + return proj[0], proj[1], proj[2] + else: + # encoder-decoder attention + w_q, w_kv = w.split([E, E * 2]) + if b is None: + b_q = b_kv = None + else: + b_q, b_kv = b.split([E, E * 2]) + q_proj = F.linear(q, w_q, b_q) + kv_proj = F.linear(k, w_kv, b_kv) + # reshape to 2, E and not E, 2 is deliberate for better memory coalescing and keeping same order as chunk() + kv_proj = kv_proj.unflatten(-1, (2, E)).unsqueeze(0).transpose(0, -2).squeeze(-2).contiguous() + return (q_proj, kv_proj[0], kv_proj[1]) + else: + w_q, w_k, w_v = w.chunk(3) + if b is None: + b_q = b_k = b_v = None + else: + b_q, b_k, b_v = b.chunk(3) + return F.linear(q, w_q, b_q), F.linear(k, w_k, b_k), F.linear(v, w_v, b_v) + +def _none_or_dtype(input: Optional[Tensor]) -> Optional[DType]: + if input is None: + return None + elif isinstance(input, torch.Tensor): + return input.dtype + raise RuntimeError("input to _none_or_dtype() must be None or torch.Tensor") +class MultiheadAttention(Module): + r"""Allows the model to jointly attend to information + from different representation subspaces as described in the paper: + `Attention Is All You Need `_. + + Multi-Head Attention is defined as: + + .. math:: + \text{MultiHead}(Q, K, V) = \text{Concat}(head_1,\dots,head_h)W^O + + where :math:`head_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)`. + + ``forward()`` will use a special optimized implementation if all of the following + conditions are met: + + - self attention is being computed (i.e., ``query``, ``key``, and ``value`` are the same tensor. This + restriction will be loosened in the future.) + - Either autograd is disabled (using ``torch.inference_mode`` or ``torch.no_grad``) or no tensor argument ``requires_grad`` + - training is disabled (using ``.eval()``) + - dropout is 0 + - ``add_bias_kv`` is ``False`` + - ``add_zero_attn`` is ``False`` + - ``batch_first`` is ``True`` and the input is batched + - ``kdim`` and ``vdim`` are equal to ``embed_dim`` + - at most one of ``key_padding_mask`` or ``attn_mask`` is passed + - if a `NestedTensor `_ is passed, neither ``key_padding_mask`` + nor ``attn_mask`` is passed + + If the optimized implementation is in use, a + `NestedTensor `_ can be passed for + ``query``/``key``/``value`` to represent padding more efficiently than using a + padding mask. In this case, a `NestedTensor `_ + will be returned, and an additional speedup proportional to the fraction of the input + that is padding can be expected. + + Args: + embed_dim: Total dimension of the model. + num_heads: Number of parallel attention heads. Note that ``embed_dim`` will be split + across ``num_heads`` (i.e. each head will have dimension ``embed_dim // num_heads``). + dropout: Dropout probability on ``attn_output_weights``. Default: ``0.0`` (no dropout). + bias: If specified, adds bias to input / output projection layers. Default: ``True``. + add_bias_kv: If specified, adds bias to the key and value sequences at dim=0. Default: ``False``. + add_zero_attn: If specified, adds a new batch of zeros to the key and value sequences at dim=1. + Default: ``False``. + kdim: Total number of features for keys. Default: ``None`` (uses ``kdim=embed_dim``). + vdim: Total number of features for values. Default: ``None`` (uses ``vdim=embed_dim``). + batch_first: If ``True``, then the input and output tensors are provided + as (batch, seq, feature). Default: ``False`` (seq, batch, feature). + + Examples:: + + >>> # xdoctest: +SKIP + >>> multihead_attn = nn.MultiheadAttention(embed_dim, num_heads) + >>> attn_output, attn_output_weights = multihead_attn(query, key, value) + + """ + __constants__ = ["batch_first"] + bias_k: Optional[torch.Tensor] + bias_v: Optional[torch.Tensor] + + def __init__( + self, + embed_dim, + num_heads, + dropout=0.0, + bias=True, + add_bias_kv=False, + add_zero_attn=False, + kdim=None, + vdim=None, + batch_first=False, + linear1_cls=Linear, + linear2_cls=Linear, + device=None, + dtype=None, + ) -> None: + factory_kwargs = {"device": device, "dtype": dtype} + super(MultiheadAttention, self).__init__() + self.embed_dim = embed_dim + self.kdim = kdim if kdim is not None else embed_dim + self.vdim = vdim if vdim is not None else embed_dim + self._qkv_same_embed_dim = ( + self.kdim == embed_dim and self.vdim == embed_dim + ) + + self.num_heads = num_heads + self.dropout = dropout + self.batch_first = batch_first + self.head_dim = embed_dim // num_heads + assert ( + self.head_dim * num_heads == self.embed_dim + ), "embed_dim must be divisible by num_heads" + + if add_bias_kv: + self.bias_k = Parameter( + torch.empty((1, 1, embed_dim), **factory_kwargs) + ) + self.bias_v = Parameter( + torch.empty((1, 1, embed_dim), **factory_kwargs) + ) + else: + self.bias_k = self.bias_v = None + + if linear1_cls == Linear: + if not self._qkv_same_embed_dim: + self.q_proj_weight = Parameter( + torch.empty((embed_dim, embed_dim), **factory_kwargs) + ) + self.k_proj_weight = Parameter( + torch.empty((embed_dim, self.kdim), **factory_kwargs) + ) + self.v_proj_weight = Parameter( + torch.empty((embed_dim, self.vdim), **factory_kwargs) + ) + self.register_parameter("in_proj_weight", None) + else: + # go down this route with voicecraft + self.in_proj_weight = Parameter( + torch.empty((3 * embed_dim, embed_dim), **factory_kwargs) + ) + self.register_parameter("q_proj_weight", None) + self.register_parameter("k_proj_weight", None) + self.register_parameter("v_proj_weight", None) + + if bias: # True by default + self.in_proj_bias = Parameter( + torch.empty(3 * embed_dim, **factory_kwargs) + ) + else: + self.register_parameter("in_proj_bias", None) + self.out_proj = NonDynamicallyQuantizableLinear( + embed_dim, embed_dim, bias=bias, **factory_kwargs + ) + + self._reset_parameters() + else: + if not self._qkv_same_embed_dim: + raise NotImplementedError + else: + self.in_proj_linear = linear1_cls( + embed_dim, 3 * embed_dim, bias=bias, **factory_kwargs + ) + self.in_proj_weight = self.in_proj_linear.weight + + self.register_parameter("q_proj_weight", None) + self.register_parameter("k_proj_weight", None) + self.register_parameter("v_proj_weight", None) + + if bias: + self.in_proj_bias = self.in_proj_linear.bias + else: + self.register_parameter("in_proj_bias", None) + + self.out_proj = linear2_cls( + embed_dim, embed_dim, bias=bias, **factory_kwargs + ) + + if self.bias_k is not None: + xavier_normal_(self.bias_k) + if self.bias_v is not None: + xavier_normal_(self.bias_v) + + self.add_zero_attn = add_zero_attn + + def _reset_parameters(self): + if self._qkv_same_embed_dim: + xavier_uniform_(self.in_proj_weight) + else: + xavier_uniform_(self.q_proj_weight) + xavier_uniform_(self.k_proj_weight) + xavier_uniform_(self.v_proj_weight) + + if self.in_proj_bias is not None: + constant_(self.in_proj_bias, 0.0) + constant_(self.out_proj.bias, 0.0) + + if self.bias_k is not None: + xavier_normal_(self.bias_k) + if self.bias_v is not None: + xavier_normal_(self.bias_v) + + def __setstate__(self, state): + # Support loading old MultiheadAttention checkpoints generated by v1.1.0 + if "_qkv_same_embed_dim" not in state: + state["_qkv_same_embed_dim"] = True + + super(MultiheadAttention, self).__setstate__(state) + + def forward( + self, + query: Tensor, + key: Tensor, + value: Tensor, + key_padding_mask: Optional[Tensor] = None, + need_weights: bool = True, + attn_mask: Optional[Tensor] = None, + average_attn_weights: bool = True, + past: Optional[Tensor] = None, + ) -> Tuple[Tensor, Optional[Tensor]]: + r""" + Args: + query: Query embeddings of shape :math:`(L, E_q)` for unbatched input, :math:`(L, N, E_q)` when ``batch_first=False`` + or :math:`(N, L, E_q)` when ``batch_first=True``, where :math:`L` is the target sequence length, + :math:`N` is the batch size, and :math:`E_q` is the query embedding dimension ``embed_dim``. + Queries are compared against key-value pairs to produce the output. + See "Attention Is All You Need" for more details. + key: Key embeddings of shape :math:`(S, E_k)` for unbatched input, :math:`(S, N, E_k)` when ``batch_first=False`` + or :math:`(N, S, E_k)` when ``batch_first=True``, where :math:`S` is the source sequence length, + :math:`N` is the batch size, and :math:`E_k` is the key embedding dimension ``kdim``. + See "Attention Is All You Need" for more details. + value: Value embeddings of shape :math:`(S, E_v)` for unbatched input, :math:`(S, N, E_v)` when + ``batch_first=False`` or :math:`(N, S, E_v)` when ``batch_first=True``, where :math:`S` is the source + sequence length, :math:`N` is the batch size, and :math:`E_v` is the value embedding dimension ``vdim``. + See "Attention Is All You Need" for more details. + key_padding_mask: If specified, a mask of shape :math:`(N, S)` indicating which elements within ``key`` + to ignore for the purpose of attention (i.e. treat as "padding"). For unbatched `query`, shape should be :math:`(S)`. + Binary and byte masks are supported. + For a binary mask, a ``True`` value indicates that the corresponding ``key`` value will be ignored for + the purpose of attention. For a float mask, it will be directly added to the corresponding ``key`` value. + need_weights: If specified, returns ``attn_output_weights`` in addition to ``attn_outputs``. + Default: ``True``. + attn_mask: If specified, a 2D or 3D mask preventing attention to certain positions. Must be of shape + :math:`(L, S)` or :math:`(N\cdot\text{num\_heads}, L, S)`, where :math:`N` is the batch size, + :math:`L` is the target sequence length, and :math:`S` is the source sequence length. A 2D mask will be + broadcasted across the batch while a 3D mask allows for a different mask for each entry in the batch. + Binary, byte, and float masks are supported. For a binary mask, a ``True`` value indicates that the + corresponding position is not allowed to attend. For a byte mask, a non-zero value indicates that the + corresponding position is not allowed to attend. For a float mask, the mask values will be added to + the attention weight. + average_attn_weights: If true, indicates that the returned ``attn_weights`` should be averaged across + heads. Otherwise, ``attn_weights`` are provided separately per head. Note that this flag only has an + effect when ``need_weights=True``. Default: ``True`` (i.e. average weights across heads) + + Outputs: + - **attn_output** - Attention outputs of shape :math:`(L, E)` when input is unbatched, + :math:`(L, N, E)` when ``batch_first=False`` or :math:`(N, L, E)` when ``batch_first=True``, + where :math:`L` is the target sequence length, :math:`N` is the batch size, and :math:`E` is the + embedding dimension ``embed_dim``. + - **attn_output_weights** - Only returned when ``need_weights=True``. If ``average_attn_weights=True``, + returns attention weights averaged across heads of shape :math:`(L, S)` when input is unbatched or + :math:`(N, L, S)`, where :math:`N` is the batch size, :math:`L` is the target sequence length, and + :math:`S` is the source sequence length. If ``average_attn_weights=False``, returns attention weights per + head of shape :math:`(\text{num\_heads}, L, S)` when input is unbatched or :math:`(N, \text{num\_heads}, L, S)`. + + .. note:: + `batch_first` argument is ignored for unbatched inputs. + """ + is_batched = query.dim() == 3 + if key_padding_mask is not None: + _kpm_dtype = key_padding_mask.dtype + if _kpm_dtype != torch.bool and not torch.is_floating_point( + key_padding_mask + ): + raise AssertionError( + "only bool and floating types of key_padding_mask are supported" + ) + why_not_fast_path = "" + if not is_batched: + why_not_fast_path = f"input not batched; expected query.dim() of 3 but got {query.dim()}" + elif query is not key or key is not value: + # When lifting this restriction, don't forget to either + # enforce that the dtypes all match or test cases where + # they don't! + why_not_fast_path = "non-self attention was used (query, key, and value are not the same Tensor)" + elif ( + self.in_proj_bias is not None + and query.dtype != self.in_proj_bias.dtype + ): + why_not_fast_path = f"dtypes of query ({query.dtype}) and self.in_proj_bias ({self.in_proj_bias.dtype}) don't match" + elif ( + self.in_proj_weight is not None + and query.dtype != self.in_proj_weight.dtype + ): + # this case will fail anyway, but at least they'll get a useful error message. + why_not_fast_path = f"dtypes of query ({query.dtype}) and self.in_proj_weight ({self.in_proj_weight.dtype}) don't match" + elif self.training: + why_not_fast_path = "training is enabled" + elif not self.batch_first: + why_not_fast_path = "batch_first was not True" + elif self.bias_k is not None: + why_not_fast_path = "self.bias_k was not None" + elif self.bias_v is not None: + why_not_fast_path = "self.bias_v was not None" + elif self.dropout: + why_not_fast_path = f"dropout was {self.dropout}, required zero" + elif self.add_zero_attn: + why_not_fast_path = "add_zero_attn was enabled" + elif not self._qkv_same_embed_dim: + why_not_fast_path = "_qkv_same_embed_dim was not True" + elif attn_mask is not None: + why_not_fast_path = "attn_mask was not None" + elif query.is_nested and key_padding_mask is not None: + why_not_fast_path = ( + "key_padding_mask is not supported with NestedTensor input" + ) + elif self.num_heads % 2 == 1: + why_not_fast_path = "num_heads is odd" + elif torch.is_autocast_enabled(): + why_not_fast_path = "autocast is enabled" + + if not why_not_fast_path: + tensor_args = ( + query, + key, + value, + self.in_proj_weight, + self.in_proj_bias, + self.out_proj.weight, + self.out_proj.bias, + ) + # We have to use list comprehensions below because TorchScript does not support + # generator expressions. + if torch.overrides.has_torch_function(tensor_args): + why_not_fast_path = "some Tensor argument has_torch_function" + elif not all( + [ + (x is None or x.is_cuda or "cpu" in str(x.device)) + for x in tensor_args + ] + ): + why_not_fast_path = ( + "some Tensor argument is neither CUDA nor CPU" + ) + elif torch.is_grad_enabled() and any( + [x is not None and x.requires_grad for x in tensor_args] + ): + why_not_fast_path = ( + "grad is enabled and at least one of query or the " + "input/output projection weights or biases requires_grad" + ) + if not why_not_fast_path: + return torch._native_multi_head_attention( + query, + key, + value, + self.embed_dim, + self.num_heads, + self.in_proj_weight, + self.in_proj_bias, + self.out_proj.weight, + self.out_proj.bias, + key_padding_mask + if key_padding_mask is not None + else attn_mask, + need_weights, + average_attn_weights, + 1 + if key_padding_mask is not None + else 0 + if attn_mask is not None + else None, + ) + + any_nested = query.is_nested or key.is_nested or value.is_nested + assert not any_nested, ( + "MultiheadAttention does not support NestedTensor outside of its fast path. " + + f"The fast path was not hit because {why_not_fast_path}" + ) + + if self.batch_first and is_batched: + # make sure that the transpose op does not affect the "is" property + if key is value: + if query is key: + query = key = value = query.transpose(1, 0) + else: + query, key = [x.transpose(1, 0) for x in (query, key)] + value = key + else: + query, key, value = [ + x.transpose(1, 0) for x in (query, key, value) + ] + + if not self._qkv_same_embed_dim: + attn_output, attn_output_weights = F.multi_head_attention_forward( + query, + key, + value, + self.embed_dim, + self.num_heads, + self.in_proj_weight, + self.in_proj_bias, + self.bias_k, + self.bias_v, + self.add_zero_attn, + self.dropout, + self.out_proj.weight, + self.out_proj.bias, + training=self.training, + key_padding_mask=key_padding_mask, + need_weights=need_weights, + attn_mask=attn_mask, + use_separate_proj_weight=True, + q_proj_weight=self.q_proj_weight, + k_proj_weight=self.k_proj_weight, + v_proj_weight=self.v_proj_weight, + average_attn_weights=average_attn_weights, + ) + else: + # re-write the self.attention here, to get k, v cache + tgt_len, bsz, embed_dim = query.shape + src_len, _, _ = key.shape + num_heads = self.num_heads + key_padding_mask = _canonical_mask( + mask=key_padding_mask, + mask_name="key_padding_mask", + other_type=_none_or_dtype(attn_mask), + other_name="attn_mask", + target_type=query.dtype + ) + attn_mask = _canonical_mask( + mask=attn_mask, + mask_name="attn_mask", + other_type=None, + other_name="", + target_type=query.dtype, + check_other=False, + ) + head_dim = self.embed_dim // self.num_heads + assert head_dim * self.num_heads == self.embed_dim, f"embed_dim {self.embed_dim} not divisible by num_heads {self.num_heads}" + assert key.shape == value.shape, f"key shape {key.shape} does not match value shape {value.shape}" + q, k, v = _in_projection_packed(query, key, value, self.in_proj_weight, self.in_proj_bias) + # k_present, v_present = k, v + + # + # reshape q, k, v for multihead attention and make em batch first + # + + q = q.view(tgt_len, bsz * num_heads, head_dim).transpose(0, 1) + k = k.view(k.shape[0], bsz * num_heads, head_dim).transpose(0, 1) + v = v.view(v.shape[0], bsz * num_heads, head_dim).transpose(0, 1) # (bsz * num_heads, src_len, head_dim) + src_len = k.size(1) + if past is not None and past.ndim > 2: + expected_src_len = src_len + past[0].shape[-2] + else: + expected_src_len = src_len + + + # ensure attn_mask's dim is 3 + if attn_mask.dim() == 2: + correct_2d_size = (tgt_len, expected_src_len) + if attn_mask.shape != correct_2d_size: + raise RuntimeError(f"The shape of the 2D attn_mask is {attn_mask.shape}, but should be {correct_2d_size}.") + attn_mask = attn_mask.unsqueeze(0) + elif attn_mask.dim() == 3: + correct_3d_size = (bsz * num_heads, tgt_len, expected_src_len) + if attn_mask.shape != correct_3d_size: + raise RuntimeError(f"The shape of the 3D attn_mask is {attn_mask.shape}, but should be {correct_3d_size}.") + else: + raise RuntimeError(f"attn_mask's dimension {attn_mask.dim()} is not supported") + + if key_padding_mask is not None: + assert key_padding_mask.shape == (bsz, expected_src_len), \ + f"expecting key_padding_mask shape of {(bsz, expected_src_len)}, but got {key_padding_mask.shape}" + key_padding_mask = key_padding_mask.view(bsz, 1, 1, expected_src_len). \ + expand(-1, num_heads, -1, -1).reshape(bsz * num_heads, 1, expected_src_len) + if attn_mask is None: + attn_mask = key_padding_mask + else: + attn_mask = attn_mask + key_padding_mask + + if not self.training: + dropout_p = 0.0 + else: + dropout_p = self.dropout + + if need_weights: + raise NotImplementedError("need_weights not implemented for voicecraft") + # B, Nt, E = q.shape + # q_scaled = q / math.sqrt(E) + + # assert not (is_causal and attn_mask is None), "FIXME: is_causal not implemented for need_weights" + + # if attn_mask is not None: + # attn_output_weights = torch.baddbmm(attn_mask, q_scaled, k.transpose(-2, -1)) + # else: + # attn_output_weights = torch.bmm(q_scaled, k.transpose(-2, -1)) + # attn_output_weights = softmax(attn_output_weights, dim=-1) + # if dropout_p > 0.0: + # attn_output_weights = dropout(attn_output_weights, p=dropout_p) + + # attn_output = torch.bmm(attn_output_weights, v) + + # attn_output = attn_output.transpose(0, 1).contiguous().view(tgt_len * bsz, embed_dim) + # attn_output = linear(attn_output, out_proj_weight, out_proj_bias) + # attn_output = attn_output.view(tgt_len, bsz, attn_output.size(1)) + + # # optionally average attention weights over heads + # attn_output_weights = attn_output_weights.view(bsz, num_heads, tgt_len, src_len) + # if average_attn_weights: + # attn_output_weights = attn_output_weights.mean(dim=1) + + # if not is_batched: + # # squeeze the output if input was unbatched + # attn_output = attn_output.squeeze(1) + # attn_output_weights = attn_output_weights.squeeze(0) + # return attn_output, attn_output_weights + else: + # attn_mask can be either (L,S) or (N*num_heads, L, S) + # if attn_mask's shape is (1, L, S) we need to unsqueeze to (1, 1, L, S) + # in order to match the input for SDPA of (N, num_heads, L, S) + if attn_mask is not None: + if attn_mask.size(0) == 1 and attn_mask.dim() == 3: + attn_mask = attn_mask.unsqueeze(0) + else: + attn_mask = attn_mask.view(bsz, num_heads, -1, expected_src_len) + + q = q.view(bsz, num_heads, tgt_len, head_dim) + k = k.view(bsz, num_heads, src_len, head_dim) + v = v.view(bsz, num_heads, src_len, head_dim) + # logging.info(f"shape of past: {past.shape}") + if past is not None: + present = torch.stack([k, v], dim=0) # (2, bsz, num_heads, src_len, head_dim) + if past.ndim > 2: # this means we use kvcache, otherwise we just pass in a placeholder, but not actually using kvcache + pk, pv = past + k = torch.cat([pk, k], dim=-2) + v = torch.cat([pv, v], dim=-2) + else: + present = None + attn_output = F.scaled_dot_product_attention(q, k, v, attn_mask, dropout_p, is_causal=False) + attn_output = attn_output.permute(2, 0, 1, 3).contiguous().view(bsz * tgt_len, embed_dim) + + attn_output = F.linear(attn_output, self.out_proj.weight, self.out_proj.bias) + attn_output = attn_output.view(tgt_len, bsz, attn_output.size(1)) + if not is_batched: + # squeeze the output if input was unbatched + attn_output = attn_output.squeeze(1) + # if self.training: + # return attn_output, None + # else: + # return (attn_output, present), None + + # harded coded, the code do not support returning attn weigths yet + attn_output_weights=None + if self.batch_first and is_batched: + return attn_output.transpose(1, 0), present + else: + return attn_output, present + diff --git a/models/modules/embedding.py b/models/modules/embedding.py new file mode 100644 index 0000000..96bf1fb --- /dev/null +++ b/models/modules/embedding.py @@ -0,0 +1,98 @@ +# cp from https://github.com/lifeiteng/vall-e/blob/main/valle/modules/embedding.py +# Copyright 2023 (authors: Feiteng Li) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +import torch +import torch.nn as nn + + +class TokenEmbedding(nn.Module): + def __init__( + self, + dim_model: int, + vocab_size: int, + dropout: float = 0.0, + ): + super().__init__() + + self.vocab_size = vocab_size + self.dim_model = dim_model + + self.dropout = torch.nn.Dropout(p=dropout) + self.word_embeddings = nn.Embedding(self.vocab_size, self.dim_model) + + @property + def weight(self) -> torch.Tensor: + return self.word_embeddings.weight + + def embedding(self, index: int) -> torch.Tensor: + return self.word_embeddings.weight[index : index + 1] + + def forward(self, x: torch.Tensor): + X = self.word_embeddings(x) + X = self.dropout(X) + + return X + + +class SinePositionalEmbedding(nn.Module): + def __init__( + self, + dim_model: int, + dropout: float = 0.0, + scale: bool = False, + alpha: bool = False, + ): + super().__init__() + self.dim_model = dim_model + self.x_scale = math.sqrt(dim_model) if scale else 1.0 + self.alpha = nn.Parameter(torch.ones(1), requires_grad=alpha) + self.dropout = torch.nn.Dropout(p=dropout) + + self.reverse = False + self.pe = None + self.extend_pe(torch.tensor(0.0).expand(1, 4000)) + + def extend_pe(self, x): + """Reset the positional encodings.""" + if self.pe is not None: + if self.pe.size(1) >= x.size(1): + if self.pe.dtype != x.dtype or self.pe.device != x.device: + self.pe = self.pe.to(dtype=x.dtype, device=x.device) + return + pe = torch.zeros(x.size(1), self.dim_model) + if self.reverse: + position = torch.arange( + x.size(1) - 1, -1, -1.0, dtype=torch.float32 + ).unsqueeze(1) + else: + position = torch.arange( + 0, x.size(1), dtype=torch.float32 + ).unsqueeze(1) + div_term = torch.exp( + torch.arange(0, self.dim_model, 2, dtype=torch.float32) + * -(math.log(10000.0) / self.dim_model) + ) + pe[:, 0::2] = torch.sin(position * div_term) + pe[:, 1::2] = torch.cos(position * div_term) + pe = pe.unsqueeze(0) + self.pe = pe.to(device=x.device, dtype=x.dtype).detach() + + def forward(self, x: torch.Tensor) -> torch.Tensor: + self.extend_pe(x) + output = x.unsqueeze(-1) if x.ndim == 2 else x + output = output * self.x_scale + self.alpha * self.pe[:, : x.size(1)] + return self.dropout(output) \ No newline at end of file diff --git a/models/modules/sampling.py b/models/modules/sampling.py new file mode 100644 index 0000000..7acdcd4 --- /dev/null +++ b/models/modules/sampling.py @@ -0,0 +1,63 @@ +import torch +import torch.nn.functional as F + +def top_k_top_p_filtering( + logits, top_k=0, top_p=1.0, filter_value=-float("Inf"), min_tokens_to_keep=1 +): + """Filter a distribution of logits using top-k and/or nucleus (top-p) filtering + Args: + logits: logits distribution shape (batch size, vocabulary size) + if top_k > 0: keep only top k tokens with highest probability (top-k filtering). + if top_p < 1.0: keep the top tokens with cumulative probability >= top_p (nucleus filtering). + Nucleus filtering is described in Holtzman et al. (http://arxiv.org/abs/1904.09751) + Make sure we keep at least min_tokens_to_keep per batch example in the output + From: https://gist.github.com/thomwolf/1a5a29f6962089e871b94cbd09daf317 + """ + if top_k > 0: + top_k = min( + max(top_k, min_tokens_to_keep), logits.size(-1) + ) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits[indices_to_remove] = filter_value + + if top_p < 1.0: + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum( + F.softmax(sorted_logits, dim=-1), dim=-1 + ) + + # Remove tokens with cumulative probability above the threshold (token with 0 are kept) + sorted_indices_to_remove = cumulative_probs > top_p + if min_tokens_to_keep > 1: + # Keep at least min_tokens_to_keep (set to min_tokens_to_keep-1 because we add the first one below) + sorted_indices_to_remove[..., :min_tokens_to_keep] = 0 + # Shift the indices to the right to keep also the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[ + ..., :-1 + ].clone() + sorted_indices_to_remove[..., 0] = 0 + + # scatter sorted tensors to original indexing + indices_to_remove = sorted_indices_to_remove.scatter( + 1, sorted_indices, sorted_indices_to_remove + ) + logits[indices_to_remove] = filter_value + return logits + +def topk_sampling(logits, top_k=10, top_p=1.0, temperature=1.0): + # temperature: (`optional`) float + # The value used to module the next token probabilities. Must be strictly positive. Default to 1.0. + # top_k: (`optional`) int + # The number of highest probability vocabulary tokens to keep for top-k-filtering. Between 1 and infinity. Default to 50. + # top_p: (`optional`) float + # The cumulative probability of parameter highest probability vocabulary tokens to keep for nucleus sampling. Must be between 0 and 1. Default to 1. + + # Temperature (higher temperature => more likely to sample low probability tokens) + if temperature != 1.0: + logits = logits / temperature + # Top-p/top-k filtering + logits = top_k_top_p_filtering(logits, top_k=top_k, top_p=top_p) + # Sample + token = torch.multinomial(F.softmax(logits, dim=-1), num_samples=1) + return token \ No newline at end of file diff --git a/models/modules/scaling.py b/models/modules/scaling.py new file mode 100644 index 0000000..cd245ea --- /dev/null +++ b/models/modules/scaling.py @@ -0,0 +1,1406 @@ +# cp from https://github.com/lifeiteng/vall-e/blob/main/valle/modules/scaling.py +# Copyright 2022 Xiaomi Corp. (authors: Daniel Povey) +# +# See ../../../../LICENSE for clarification regarding multiple authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import collections +import logging +import random +import math +from functools import reduce +from itertools import repeat +from typing import Optional, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch import Tensor +from torch.nn import Embedding as ScaledEmbedding + +# from valle.utils import Transpose + +class Transpose(nn.Identity): + """(N, T, D) -> (N, D, T)""" + + def forward(self, input: torch.Tensor) -> torch.Tensor: + return input.transpose(1, 2) + +class ActivationBalancerFunction(torch.autograd.Function): + @staticmethod + def forward( + ctx, + x: Tensor, + scale_factor: Tensor, + sign_factor: Optional[Tensor], + channel_dim: int, + ) -> Tensor: + if channel_dim < 0: + channel_dim += x.ndim + ctx.channel_dim = channel_dim + xgt0 = x > 0 + if sign_factor is None: + ctx.save_for_backward(xgt0, scale_factor) + else: + ctx.save_for_backward(xgt0, scale_factor, sign_factor) + return x + + @staticmethod + def backward(ctx, x_grad: Tensor) -> Tuple[Tensor, None, None, None]: + if len(ctx.saved_tensors) == 3: + xgt0, scale_factor, sign_factor = ctx.saved_tensors + for _ in range(ctx.channel_dim, x_grad.ndim - 1): + scale_factor = scale_factor.unsqueeze(-1) + sign_factor = sign_factor.unsqueeze(-1) + factor = sign_factor + scale_factor * (xgt0.to(x_grad.dtype) - 0.5) + else: + xgt0, scale_factor = ctx.saved_tensors + for _ in range(ctx.channel_dim, x_grad.ndim - 1): + scale_factor = scale_factor.unsqueeze(-1) + factor = scale_factor * (xgt0.to(x_grad.dtype) - 0.5) + neg_delta_grad = x_grad.abs() * factor + return ( + x_grad - neg_delta_grad, + None, + None, + None, + ) + + +def _compute_scale_factor( + x: Tensor, + channel_dim: int, + min_abs: float, + max_abs: float, + gain_factor: float, + max_factor: float, +) -> Tensor: + if channel_dim < 0: + channel_dim += x.ndim + sum_dims = [d for d in range(x.ndim) if d != channel_dim] + x_abs_mean = torch.mean(x.abs(), dim=sum_dims).to(torch.float32) + + if min_abs == 0.0: + below_threshold = 0.0 + else: + # below_threshold is 0 if x_abs_mean > min_abs, can be at most max_factor if + # x_abs)_mean , min_abs. + below_threshold = ( + (min_abs - x_abs_mean) * (gain_factor / min_abs) + ).clamp(min=0, max=max_factor) + + above_threshold = ((x_abs_mean - max_abs) * (gain_factor / max_abs)).clamp( + min=0, max=max_factor + ) + + return below_threshold - above_threshold + + +def _compute_sign_factor( + x: Tensor, + channel_dim: int, + min_positive: float, + max_positive: float, + gain_factor: float, + max_factor: float, +) -> Tensor: + if channel_dim < 0: + channel_dim += x.ndim + sum_dims = [d for d in range(x.ndim) if d != channel_dim] + proportion_positive = torch.mean((x > 0).to(torch.float32), dim=sum_dims) + if min_positive == 0.0: + factor1 = 0.0 + else: + # 0 if proportion_positive >= min_positive, else can be + # as large as max_factor. + factor1 = ( + (min_positive - proportion_positive) * (gain_factor / min_positive) + ).clamp_(min=0, max=max_factor) + + if max_positive == 1.0: + factor2 = 0.0 + else: + # 0 if self.proportion_positive <= max_positive, else can be + # as large as -max_factor. + factor2 = ( + (proportion_positive - max_positive) + * (gain_factor / (1.0 - max_positive)) + ).clamp_(min=0, max=max_factor) + sign_factor = factor1 - factor2 + # require min_positive != 0 or max_positive != 1: + assert not isinstance(sign_factor, float) + return sign_factor + + +class ActivationScaleBalancerFunction(torch.autograd.Function): + """ + This object is used in class ActivationBalancer when the user specified + min_positive=0, max_positive=1, so there are no constraints on the signs + of the activations and only the absolute value has a constraint. + """ + + @staticmethod + def forward( + ctx, + x: Tensor, + sign_factor: Tensor, + scale_factor: Tensor, + channel_dim: int, + ) -> Tensor: + if channel_dim < 0: + channel_dim += x.ndim + ctx.channel_dim = channel_dim + xgt0 = x > 0 + ctx.save_for_backward(xgt0, sign_factor, scale_factor) + return x + + @staticmethod + def backward(ctx, x_grad: Tensor) -> Tuple[Tensor, None, None, None]: + xgt0, sign_factor, scale_factor = ctx.saved_tensors + for _ in range(ctx.channel_dim, x_grad.ndim - 1): + sign_factor = sign_factor.unsqueeze(-1) + scale_factor = scale_factor.unsqueeze(-1) + + factor = sign_factor + scale_factor * (xgt0.to(x_grad.dtype) - 0.5) + neg_delta_grad = x_grad.abs() * factor + return ( + x_grad - neg_delta_grad, + None, + None, + None, + ) + + +class RandomClampFunction(torch.autograd.Function): + @staticmethod + def forward( + ctx, + x: Tensor, + min: Optional[float], + max: Optional[float], + prob: float, + reflect: float, + ) -> Tensor: + x_clamped = torch.clamp(x, min=min, max=max) + mask = torch.rand_like(x) < prob + ans = torch.where(mask, x_clamped, x) + if x.requires_grad: + ctx.save_for_backward(ans == x) + ctx.reflect = reflect + if reflect != 0.0: + ans = ans * (1.0 + reflect) - (x * reflect) + return ans + + @staticmethod + def backward( + ctx, ans_grad: Tensor + ) -> Tuple[Tensor, None, None, None, None]: + (is_same,) = ctx.saved_tensors + x_grad = ans_grad * is_same.to(ans_grad.dtype) + reflect = ctx.reflect + if reflect != 0.0: + x_grad = x_grad * (1.0 + reflect) - (ans_grad * reflect) + return x_grad, None, None, None, None + + +def random_clamp( + x: Tensor, + min: Optional[float] = None, + max: Optional[float] = None, + prob: float = 0.5, + reflect: float = 0.0, +): + return RandomClampFunction.apply(x, min, max, prob, reflect) + + +def random_cast_to_half(x: Tensor, min_abs: float = 5.0e-06) -> Tensor: + """ + A randomized way of casting a floating point value to half precision. + """ + if x.dtype == torch.float16: + return x + x_abs = x.abs() + is_too_small = x_abs < min_abs + # for elements where is_too_small is true, random_val will contain +-min_abs with + # probability (x.abs() / min_abs), and 0.0 otherwise. [so this preserves expectations, + # for those elements]. + random_val = min_abs * x.sign() * (torch.rand_like(x) * min_abs < x_abs) + return torch.where(is_too_small, random_val, x).to(torch.float16) + + +class RandomGradFunction(torch.autograd.Function): + """ + Does nothing in forward pass; in backward pass, gets rid of very small grads using + randomized approach that preserves expectations (intended to reduce roundoff). + """ + + @staticmethod + def forward(ctx, x: Tensor, min_abs: float) -> Tensor: + ctx.min_abs = min_abs + return x + + @staticmethod + def backward(ctx, ans_grad: Tensor) -> Tuple[Tensor, None]: + if ans_grad.dtype == torch.float16: + return ( + random_cast_to_half( + ans_grad.to(torch.float32), min_abs=ctx.min_abs + ), + None, + ) + else: + return ans_grad, None + + +class RandomGrad(torch.nn.Module): + """ + Gets rid of very small gradients using an expectation-preserving method, intended to increase + accuracy of training when using amp (automatic mixed precision) + """ + + def __init__(self, min_abs: float = 5.0e-06): + super(RandomGrad, self).__init__() + self.min_abs = min_abs + + def forward(self, x: Tensor): + if ( + torch.jit.is_scripting() + or not self.training + or torch.jit.is_tracing() + ): + return x + else: + return RandomGradFunction.apply(x, self.min_abs) + + +class SoftmaxFunction(torch.autograd.Function): + """ + Tries to handle half-precision derivatives in a randomized way that should + be more accurate for training than the default behavior. + """ + + @staticmethod + def forward(ctx, x: Tensor, dim: int): + ans = x.softmax(dim=dim) + # if x dtype is float16, x.softmax() returns a float32 because + # (presumably) that op does not support float16, and autocast + # is enabled. + if torch.is_autocast_enabled(): + ans = ans.to(torch.float16) + ctx.save_for_backward(ans) + ctx.x_dtype = x.dtype + ctx.dim = dim + return ans + + @staticmethod + def backward(ctx, ans_grad: Tensor): + (ans,) = ctx.saved_tensors + with torch.cuda.amp.autocast(enabled=False): + ans_grad = ans_grad.to(torch.float32) + ans = ans.to(torch.float32) + x_grad = ans_grad * ans + x_grad = x_grad - ans * x_grad.sum(dim=ctx.dim, keepdim=True) + return x_grad, None + + +def softmax(x: Tensor, dim: int): + if torch.jit.is_scripting() or torch.jit.is_tracing(): + return x.softmax(dim) + + return SoftmaxFunction.apply(x, dim) + + +class MaxEigLimiterFunction(torch.autograd.Function): + @staticmethod + def forward( + ctx, + x: Tensor, + coeffs: Tensor, + direction: Tensor, + channel_dim: int, + grad_scale: float, + ) -> Tensor: + ctx.channel_dim = channel_dim + ctx.grad_scale = grad_scale + ctx.save_for_backward(x.detach(), coeffs.detach(), direction.detach()) + return x + + @staticmethod + def backward(ctx, x_grad, *args): + with torch.enable_grad(): + (x_orig, coeffs, new_direction) = ctx.saved_tensors + x_orig.requires_grad = True + num_channels = x_orig.shape[ctx.channel_dim] + x = x_orig.transpose(ctx.channel_dim, -1).reshape(-1, num_channels) + new_direction.requires_grad = False + x = x - x.mean(dim=0) + x_var = (x ** 2).mean() + x_residual = x - coeffs * new_direction + x_residual_var = (x_residual ** 2).mean() + # `variance_proportion` is the proportion of the variance accounted for + # by the top eigen-direction. This is to be minimized. + variance_proportion = (x_var - x_residual_var) / (x_var + 1.0e-20) + variance_proportion.backward() + x_orig_grad = x_orig.grad + x_extra_grad = ( + x_orig.grad + * ctx.grad_scale + * x_grad.norm() + / (x_orig_grad.norm() + 1.0e-20) + ) + return x_grad + x_extra_grad.detach(), None, None, None, None + + +class BasicNorm(torch.nn.Module): + """ + This is intended to be a simpler, and hopefully cheaper, replacement for + LayerNorm. The observation this is based on, is that Transformer-type + networks, especially with pre-norm, sometimes seem to set one of the + feature dimensions to a large constant value (e.g. 50), which "defeats" + the LayerNorm because the output magnitude is then not strongly dependent + on the other (useful) features. Presumably the weight and bias of the + LayerNorm are required to allow it to do this. + + So the idea is to introduce this large constant value as an explicit + parameter, that takes the role of the "eps" in LayerNorm, so the network + doesn't have to do this trick. We make the "eps" learnable. + + Args: + num_channels: the number of channels, e.g. 512. + channel_dim: the axis/dimension corresponding to the channel, + interprted as an offset from the input's ndim if negative. + shis is NOT the num_channels; it should typically be one of + {-2, -1, 0, 1, 2, 3}. + eps: the initial "epsilon" that we add as ballast in: + scale = ((input_vec**2).mean() + epsilon)**-0.5 + Note: our epsilon is actually large, but we keep the name + to indicate the connection with conventional LayerNorm. + learn_eps: if true, we learn epsilon; if false, we keep it + at the initial value. + eps_min: float + eps_max: float + """ + + def __init__( + self, + num_channels: int, + channel_dim: int = -1, # CAUTION: see documentation. + eps: float = 0.25, + learn_eps: bool = True, + eps_min: float = -3.0, + eps_max: float = 3.0, + ) -> None: + super(BasicNorm, self).__init__() + self.num_channels = num_channels + self.channel_dim = channel_dim + if learn_eps: + self.eps = nn.Parameter(torch.tensor(eps).log().detach()) + else: + self.register_buffer("eps", torch.tensor(eps).log().detach()) + self.eps_min = eps_min + self.eps_max = eps_max + + def forward(self, x: Tensor) -> Tensor: + assert x.shape[self.channel_dim] == self.num_channels + eps = self.eps + if self.training and random.random() < 0.25: + # with probability 0.25, in training mode, clamp eps between the min + # and max; this will encourage it to learn parameters within the + # allowed range by making parameters that are outside the allowed + # range noisy. + + # gradients to allow the parameter to get back into the allowed region if it happens to exit it. + eps = eps.clamp(min=self.eps_min, max=self.eps_max) + scales = ( + torch.mean(x ** 2, dim=self.channel_dim, keepdim=True) + eps.exp() + ) ** -0.5 + return x * scales + + +def ScaledLinear(*args, initial_scale: float = 1.0, **kwargs) -> nn.Linear: + """ + Behaves like a constructor of a modified version of nn.Linear + that gives an easy way to set the default initial parameter scale. + + Args: + Accepts the standard args and kwargs that nn.Linear accepts + e.g. in_features, out_features, bias=False. + + initial_scale: you can override this if you want to increase + or decrease the initial magnitude of the module's output + (affects the initialization of weight_scale and bias_scale). + Another option, if you want to do something like this, is + to re-initialize the parameters. + """ + ans = nn.Linear(*args, **kwargs) + with torch.no_grad(): + ans.weight[:] *= initial_scale + if ans.bias is not None: + torch.nn.init.uniform_( + ans.bias, -0.1 * initial_scale, 0.1 * initial_scale + ) + return ans + + +def ScaledConv1d( + *args, + initial_scale: float = 1.0, + kernel_size: int = 3, + padding: str = "same", + **kwargs, +) -> nn.Conv1d: + """ + Behaves like a constructor of a modified version of nn.Conv1d + that gives an easy way to set the default initial parameter scale. + + Args: + Accepts the standard args and kwargs that nn.Linear accepts + e.g. in_features, out_features, bias=False. + + initial_scale: you can override this if you want to increase + or decrease the initial magnitude of the module's output + (affects the initialization of weight_scale and bias_scale). + Another option, if you want to do something like this, is + to re-initialize the parameters. + """ + ans = nn.Conv1d(*args, kernel_size=kernel_size, padding=padding, **kwargs) + with torch.no_grad(): + ans.weight[:] *= initial_scale + if ans.bias is not None: + torch.nn.init.uniform_( + ans.bias, -0.1 * initial_scale, 0.1 * initial_scale + ) + return ans + + +def TransposeScaledConv1d( + *args, + initial_scale: float = 1.0, + kernel_size: int = 3, + padding: str = "same", + **kwargs, +) -> nn.Sequential: + """ + Transpose -> ScaledConv1d + """ + return nn.Sequential( + Transpose(), + ScaledConv1d( + *args, + initial_scale=initial_scale, + kernel_size=kernel_size, + padding=padding, + **kwargs, + ), + ) + + +def ScaledConv1dTranspose( + *args, + initial_scale: float = 1.0, + kernel_size: int = 3, + padding: str = "same", + **kwargs, +) -> nn.Sequential: + """ + Transpose -> ScaledConv1d + """ + return nn.Sequential( + ScaledConv1d( + *args, + initial_scale=initial_scale, + kernel_size=kernel_size, + padding=padding, + **kwargs, + ), + Transpose(), + ) + + +def TransposeConv1d( + *args, kernel_size: int = 3, padding: str = "same", **kwargs +) -> nn.Sequential: + """ + Transpose -> Conv1d + """ + return nn.Sequential( + Transpose(), + nn.Conv1d(*args, kernel_size=kernel_size, padding=padding, **kwargs), + ) + + +def Conv1dTranspose( + *args, kernel_size: int = 3, padding: str = "same", **kwargs +) -> nn.Sequential: + """ + ScaledConv1d -> Transpose + """ + return nn.Sequential( + nn.Conv1d(*args, kernel_size=kernel_size, padding=padding, **kwargs), + Transpose(), + ) + + +class SRLinear(nn.Linear): + """https://arxiv.org/abs/2303.06296 + Stabilizing Transformer Training by Preventing Attention Entropy Collapse + """ + + def __init__(self, in_features, out_features, bias=True, **kwargs): + super().__init__(in_features, out_features, bias=bias, **kwargs) + self.register_buffer( + "u", nn.functional.normalize(torch.randn(in_features), dim=0) + ) + with torch.no_grad(): + sigma = self.get_sigma() + self.register_buffer("spectral_norm", sigma) + self.sigma = nn.Parameter(torch.ones(1)) + + def get_sigma(self): + with torch.no_grad(): + u = self.u + v = self.weight.mv(u) + v = nn.functional.normalize(v, dim=0) + u = self.weight.T.mv(v) + u = nn.functional.normalize(u, dim=0) + self.u.data.copy_(u) + return torch.einsum("c,cd,d->", v, self.weight, u) + + def get_weight(self): + sigma = self.get_sigma() + if self.training: + self.spectral_norm.data.copy_(sigma) + weight = (self.sigma / sigma) * self.weight + return weight + + def forward(self, x): + return nn.functional.linear(x, self.get_weight(), self.bias) + + +class SRConv1d(SRLinear): + def __init__( + self, + in_features, + out_features, + kernel_size, + stride: int = 1, + padding: str = "same", + bias: bool = True, + **kwargs, + ): + in_features = in_features * kernel_size + super().__init__(in_features, out_features, bias=bias, **kwargs) + nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5)) + self.kernel_size = kernel_size + self.stride = stride + self.padding = padding + + def forward(self, x): + in_features = self.in_features // self.kernel_size + weight = self.get_weight().view( + self.out_features, in_features, self.kernel_size + ) + return nn.functional.conv1d( + x, weight, bias=self.bias, stride=self.stride, padding=self.padding + ) + + +def TransposeSRConv1d( + *args, kernel_size: int = 3, padding: str = "same", **kwargs +) -> nn.Sequential: + """ + Transpose -> SRConv1d + """ + return nn.Sequential( + Transpose(), + SRConv1d(*args, kernel_size=kernel_size, padding=padding, **kwargs), + ) + + +def SRConv1dTranspose( + *args, kernel_size: int = 3, padding: str = "same", **kwargs +) -> nn.Sequential: + """ + SRConv1d -> Transpose + """ + return nn.Sequential( + SRConv1d(*args, kernel_size=kernel_size, padding=padding, **kwargs), + Transpose(), + ) + + +class ActivationBalancer(torch.nn.Module): + """ + Modifies the backpropped derivatives of a function to try to encourage, for + each channel, that it is positive at least a proportion `threshold` of the + time. It does this by multiplying negative derivative values by up to + (1+max_factor), and positive derivative values by up to (1-max_factor), + interpolated from 1 at the threshold to those extremal values when none + of the inputs are positive. + + Args: + num_channels: the number of channels + channel_dim: the dimension/axis corresponding to the channel, e.g. + -1, 0, 1, 2; will be interpreted as an offset from x.ndim if negative. + min_positive: the minimum, per channel, of the proportion of the time + that (x > 0), below which we start to modify the derivatives. + max_positive: the maximum, per channel, of the proportion of the time + that (x > 0), above which we start to modify the derivatives. + max_factor: the maximum factor by which we modify the derivatives for + either the sign constraint or the magnitude constraint; + e.g. with max_factor=0.02, the the derivatives would be multiplied by + values in the range [0.98..1.02]. + sign_gain_factor: determines the 'gain' with which we increase the + change in gradient once the constraints on min_positive and max_positive + are violated. + scale_gain_factor: determines the 'gain' with which we increase the + change in gradient once the constraints on min_abs and max_abs + are violated. + min_abs: the minimum average-absolute-value difference from the mean + value per channel, which we allow, before we start to modify + the derivatives to prevent this. + max_abs: the maximum average-absolute-value difference from the mean + value per channel, which we allow, before we start to modify + the derivatives to prevent this. + min_prob: determines the minimum probability with which we modify the + gradients for the {min,max}_positive and {min,max}_abs constraints, + on each forward(). This is done randomly to prevent all layers + from doing it at the same time. Early in training we may use + higher probabilities than this; it will decay to this value. + """ + + def __init__( + self, + num_channels: int, + channel_dim: int, + min_positive: float = 0.05, + max_positive: float = 0.95, + max_factor: float = 0.04, + sign_gain_factor: float = 0.01, + scale_gain_factor: float = 0.02, + min_abs: float = 0.2, + max_abs: float = 100.0, + min_prob: float = 0.1, + ): + super(ActivationBalancer, self).__init__() + self.num_channels = num_channels + self.channel_dim = channel_dim + self.min_positive = min_positive + self.max_positive = max_positive + self.max_factor = max_factor + self.min_abs = min_abs + self.max_abs = max_abs + self.min_prob = min_prob + self.sign_gain_factor = sign_gain_factor + self.scale_gain_factor = scale_gain_factor + + # count measures how many times the forward() function has been called. + # We occasionally sync this to a tensor called `count`, that exists to + # make sure it is synced to disk when we load and save the model. + self.cpu_count = 0 + self.register_buffer("count", torch.tensor(0, dtype=torch.int64)) + + def forward(self, x: Tensor) -> Tensor: + if ( + torch.jit.is_scripting() + or not x.requires_grad + or torch.jit.is_tracing() + ): + return _no_op(x) + + count = self.cpu_count + self.cpu_count += 1 + + if random.random() < 0.01: + # Occasionally sync self.cpu_count with self.count. + # count affects the decay of 'prob'. don't do this on every iter, + # because syncing with the GPU is slow. + self.cpu_count = max(self.cpu_count, self.count.item()) + self.count.fill_(self.cpu_count) + + # the prob of doing some work exponentially decreases from 0.5 till it hits + # a floor at min_prob (==0.1, by default) + prob = max(self.min_prob, 0.5 ** (1 + (count / 4000.0))) + + if random.random() < prob: + sign_gain_factor = 0.5 + if self.min_positive != 0.0 or self.max_positive != 1.0: + sign_factor = _compute_sign_factor( + x, + self.channel_dim, + self.min_positive, + self.max_positive, + gain_factor=self.sign_gain_factor / prob, + max_factor=self.max_factor, + ) + else: + sign_factor = None + + scale_factor = _compute_scale_factor( + x.detach(), + self.channel_dim, + min_abs=self.min_abs, + max_abs=self.max_abs, + gain_factor=self.scale_gain_factor / prob, + max_factor=self.max_factor, + ) + return ActivationBalancerFunction.apply( + x, + scale_factor, + sign_factor, + self.channel_dim, + ) + else: + return _no_op(x) + + +def penalize_abs_values_gt(x: Tensor, limit: float, penalty: float) -> Tensor: + """ + Returns x unmodified, but in backprop will put a penalty for the excess of + the absolute values of elements of x over the limit "limit". E.g. if + limit == 10.0, then if x has any values over 10 it will get a penalty. + + Caution: the value of this penalty will be affected by grad scaling used + in automatic mixed precision training. For this reasons we use this, + it shouldn't really matter, or may even be helpful; we just use this + to disallow really implausible values of scores to be given to softmax. + """ + x_sign = x.sign() + over_limit = (x.abs() - limit) > 0 + # The following is a memory efficient way to penalize the absolute values of + # x that's over the limit. (The memory efficiency comes when you think + # about which items torch needs to cache for the autograd, and which ones it + # can throw away). The numerical value of aux_loss as computed here will + # actually be larger than it should be, by limit * over_limit.sum(), but it + # has the same derivative as the real aux_loss which is penalty * (x.abs() - + # limit).relu(). + aux_loss = penalty * ((x_sign * over_limit).to(torch.int8) * x) + # note: we don't do sum() here on aux)_loss, but it's as if we had done + # sum() due to how with_loss() works. + x = with_loss(x, aux_loss) + # you must use x for something, or this will be ineffective. + return x + + +def _diag(x: Tensor): # like .diag(), but works for tensors with 3 dims. + if x.ndim == 2: + return x.diag() + else: + (batch, dim, dim) = x.shape + x = x.reshape(batch, dim * dim) + x = x[:, :: dim + 1] + assert x.shape == (batch, dim) + return x + + +def _whitening_metric(x: Tensor, num_groups: int): + """ + Computes the "whitening metric", a value which will be 1.0 if all the eigenvalues of + of the centered feature covariance are the same within each group's covariance matrix + and also between groups. + Args: + x: a Tensor of shape (*, num_channels) + num_groups: the number of groups of channels, a number >=1 that divides num_channels + Returns: + Returns a scalar Tensor that will be 1.0 if the data is "perfectly white" and + greater than 1.0 otherwise. + """ + assert x.dtype != torch.float16 + x = x.reshape(-1, x.shape[-1]) + (num_frames, num_channels) = x.shape + assert num_channels % num_groups == 0 + channels_per_group = num_channels // num_groups + x = x.reshape(num_frames, num_groups, channels_per_group).transpose(0, 1) + # x now has shape (num_groups, num_frames, channels_per_group) + # subtract the mean so we use the centered, not uncentered, covariance. + # My experience has been that when we "mess with the gradients" like this, + # it's better not do anything that tries to move the mean around, because + # that can easily cause instability. + x = x - x.mean(dim=1, keepdim=True) + # x_covar: (num_groups, channels_per_group, channels_per_group) + x_covar = torch.matmul(x.transpose(1, 2), x) + x_covar_mean_diag = _diag(x_covar).mean() + # the following expression is what we'd get if we took the matrix product + # of each covariance and measured the mean of its trace, i.e. + # the same as _diag(torch.matmul(x_covar, x_covar)).mean(). + x_covarsq_mean_diag = (x_covar ** 2).sum() / ( + num_groups * channels_per_group + ) + # this metric will be >= 1.0; the larger it is, the less 'white' the data was. + metric = x_covarsq_mean_diag / (x_covar_mean_diag ** 2 + 1.0e-20) + return metric + + +class WhiteningPenaltyFunction(torch.autograd.Function): + @staticmethod + def forward( + ctx, + x: Tensor, + num_groups: int, + whitening_limit: float, + grad_scale: float, + ) -> Tensor: + ctx.save_for_backward(x) + ctx.num_groups = num_groups + ctx.whitening_limit = whitening_limit + ctx.grad_scale = grad_scale + return x + + @staticmethod + def backward(ctx, x_grad: Tensor): + (x_orig,) = ctx.saved_tensors + with torch.enable_grad(): + with torch.cuda.amp.autocast(enabled=False): + x_detached = x_orig.to(torch.float32).detach() + x_detached.requires_grad = True + + metric = _whitening_metric(x_detached, ctx.num_groups) + + if random.random() < 0.005 or __name__ == "__main__": + logging.info( + f"Whitening: num_groups={ctx.num_groups}, num_channels={x_orig.shape[-1]}, " + f"metric={metric.item():.2f} vs. limit={ctx.whitening_limit}" + ) + + (metric - ctx.whitening_limit).relu().backward() + penalty_grad = x_detached.grad + scale = ctx.grad_scale * ( + x_grad.to(torch.float32).norm() + / (penalty_grad.norm() + 1.0e-20) + ) + penalty_grad = penalty_grad * scale + return x_grad + penalty_grad.to(x_grad.dtype), None, None, None + + +class Whiten(nn.Module): + def __init__( + self, + num_groups: int, + whitening_limit: float, + prob: Union[float, Tuple[float, float]], + grad_scale: float, + ): + """ + Args: + num_groups: the number of groups to divide the channel dim into before + whitening. We will attempt to make the feature covariance + within each group, after mean subtraction, as "white" as possible, + while having the same trace across all groups. + whitening_limit: a value greater than 1.0, that dictates how much + freedom we have to violate the constraints. 1.0 would mean perfectly + white, with exactly the same trace across groups; larger values + give more freedom. E.g. 2.0. + prob: the probability with which we apply the gradient modification + (also affects the grad scale). May be supplied as a float, + or as a pair (min_prob, max_prob) + + grad_scale: determines the scale on the gradient term from this object, + relative to the rest of the gradient on the attention weights. + E.g. 0.02 (you may want to use smaller values than this if prob is large) + """ + super(Whiten, self).__init__() + assert num_groups >= 1 + assert whitening_limit >= 1 + assert grad_scale >= 0 + self.num_groups = num_groups + self.whitening_limit = whitening_limit + if isinstance(prob, float): + assert 0 < prob <= 1 + self.prob = prob + else: + (self.min_prob, self.max_prob) = prob + assert 0 < self.min_prob < self.max_prob <= 1 + self.prob = self.max_prob + + self.grad_scale = grad_scale + + def forward(self, x: Tensor) -> Tensor: + """ + In the forward pass, this function just returns the input unmodified. + In the backward pass, it will modify the gradients to ensure that the + distribution in each group has close to (lambda times I) as the covariance + after mean subtraction, with the same lambda across groups. + For whitening_limit > 1, there will be more freedom to violate this + constraint. + + Args: + x: the input of shape (*, num_channels) + + Returns: + x, unmodified. You should make sure + you use the returned value, or the graph will be freed + and nothing will happen in backprop. + """ + if ( + not x.requires_grad + or random.random() > self.prob + or self.grad_scale == 0 + ): + return _no_op(x) + else: + if hasattr(self, "min_prob") and random.random() < 0.25: + # occasionally switch between min_prob and max_prob, based on whether + # we are above or below the threshold. + if ( + _whitening_metric(x.to(torch.float32), self.num_groups) + > self.whitening_limit + ): + # there would be a change to the grad. + self.prob = self.max_prob + else: + self.prob = self.min_prob + + return WhiteningPenaltyFunction.apply( + x, self.num_groups, self.whitening_limit, self.grad_scale + ) + + +class WithLoss(torch.autograd.Function): + @staticmethod + def forward(ctx, x: Tensor, y: Tensor): + ctx.y_shape = y.shape + return x + + @staticmethod + def backward(ctx, ans_grad: Tensor): + return ans_grad, torch.ones( + ctx.y_shape, dtype=ans_grad.dtype, device=ans_grad.device + ) + + +def with_loss(x, y): + if torch.jit.is_scripting() or torch.jit.is_tracing(): + return x + # returns x but adds y.sum() to the loss function. + return WithLoss.apply(x, y) + + +def _no_op(x: Tensor) -> Tensor: + if torch.jit.is_scripting() or torch.jit.is_tracing(): + return x + else: + # a no-op function that will have a node in the autograd graph, + # to avoid certain bugs relating to backward hooks + return x.chunk(1, dim=-1)[0] + + +class Identity(torch.nn.Module): + def __init__(self): + super(Identity, self).__init__() + + def forward(self, x): + return _no_op(x) + + +class MaxEig(torch.nn.Module): + """ + Modifies the backpropped derivatives of a function to try to discourage + that any given direction in activation space accounts for more than + a specified proportion of the covariance (e.g. 0.2). + + + Args: + num_channels: the number of channels + channel_dim: the dimension/axis corresponding to the channel, e.g. + -1, 0, 1, 2; will be interpreted as an offset from x.ndim if negative. + max_var_per_eig: the maximum proportion of the variance of the + features/channels, after mean subtraction, that can come from + any given eigenvalue. + min_prob: the minimum probability with which we apply this during any invocation + of forward(), assuming last time we applied the constraint it was + not active; supplied for speed. + scale: determines the scale with which we modify the gradients, relative + to the existing / unmodified gradients + """ + + def __init__( + self, + num_channels: int, + channel_dim: int, + max_var_per_eig: float = 0.2, + min_prob: float = 0.01, + scale: float = 0.01, + ): + super(MaxEig, self).__init__() + self.num_channels = num_channels + self.channel_dim = channel_dim + self.scale = scale + assert max_var_per_eig == 0.0 or max_var_per_eig > 1.0 / num_channels + self.max_var_per_eig = max_var_per_eig + + # we figure out the dominant direction using the power method: starting with + # a random vector, keep multiplying by the covariance and renormalizing. + with torch.no_grad(): + # arbitrary.. would use randn() but want to leave the rest of the model's + # random parameters unchanged for comparison + direction = torch.arange(num_channels).to(torch.float) + direction = direction / direction.norm() + self.register_buffer("max_eig_direction", direction) + + self.min_prob = min_prob + # cur_prob is the current probability we'll use to apply the ActivationBalancer. + # We'll regress this towards prob, each tiem we try to apply it and it is not + # active. + self.cur_prob = 1.0 + + def forward(self, x: Tensor) -> Tensor: + if ( + torch.jit.is_scripting() + or self.max_var_per_eig <= 0 + or random.random() > self.cur_prob + or torch.jit.is_tracing() + ): + return _no_op(x) + + with torch.cuda.amp.autocast(enabled=False): + eps = 1.0e-20 + orig_x = x + x = x.to(torch.float32) + with torch.no_grad(): + x = x.transpose(self.channel_dim, -1).reshape( + -1, self.num_channels + ) + x = x - x.mean(dim=0) + new_direction, coeffs = self._find_direction_coeffs( + x, self.max_eig_direction + ) + x_var = (x ** 2).mean() + x_residual = x - coeffs * new_direction + x_residual_var = (x_residual ** 2).mean() + + # `variance_proportion` is the proportion of the variance accounted for + # by the top eigen-direction. + variance_proportion = (x_var - x_residual_var) / ( + x_var + 1.0e-20 + ) + + # ensure new direction is nonzero even if x == 0, by including `direction`. + self._set_direction( + 0.1 * self.max_eig_direction + new_direction + ) + + if random.random() < 0.01 or __name__ == "__main__": + logging.info( + f"variance_proportion = {variance_proportion.item()}, shape={tuple(orig_x.shape)}, cur_prob={self.cur_prob}" + ) + + if variance_proportion >= self.max_var_per_eig: + # The constraint is active. Note, we should quite rarely + # reach here, only near the beginning of training if we are + # starting to diverge, should this constraint be active. + cur_prob = self.cur_prob + self.cur_prob = ( + 1.0 # next time, do the update with probability 1.0. + ) + return MaxEigLimiterFunction.apply( + orig_x, coeffs, new_direction, self.channel_dim, self.scale + ) + else: + # let self.cur_prob exponentially approach self.min_prob, as + # long as the constraint is inactive. + self.cur_prob = 0.75 * self.cur_prob + 0.25 * self.min_prob + return orig_x + + def _set_direction(self, direction: Tensor): + """ + Sets self.max_eig_direction to a normalized version of `direction` + """ + direction = direction.detach() + direction = direction / direction.norm() + direction_sum = direction.sum().item() + if direction_sum - direction_sum == 0: # no inf/nan + self.max_eig_direction[:] = direction + else: + logging.info( + f"Warning: sum of direction in MaxEig is {direction_sum}, " + "num_channels={self.num_channels}, channel_dim={self.channel_dim}" + ) + + def _find_direction_coeffs( + self, x: Tensor, prev_direction: Tensor + ) -> Tuple[Tensor, Tensor, Tensor]: + """ + Figure out (an approximation to) the proportion of the variance of a set of + feature vectors that can be attributed to the top eigen-direction. + Args: + x: a Tensor of shape (num_frames, num_channels), with num_frames > 1. + prev_direction: a Tensor of shape (num_channels,), that is our previous estimate + of the top eigen-direction, or a random direction if this is the first + iteration. Does not have to be normalized, but should be nonzero. + + Returns: (cur_direction, coeffs), where: + cur_direction: a Tensor of shape (num_channels,) that is the current + estimate of the top eigen-direction. + coeffs: a Tensor of shape (num_frames, 1) that minimizes, or + approximately minimizes, (x - coeffs * cur_direction).norm() + """ + (num_frames, num_channels) = x.shape + assert num_channels > 1 and num_frames > 1 + assert prev_direction.shape == (num_channels,) + # `coeffs` are the coefficients of `prev_direction` in x. + # actually represent the coeffs up to a constant positive factor. + coeffs = (x * prev_direction).sum(dim=1, keepdim=True) + 1.0e-10 + cur_direction = (x * coeffs).sum(dim=0) / ( + (coeffs ** 2).sum() + 1.0e-20 + ) + return cur_direction, coeffs + + +class DoubleSwishFunction(torch.autograd.Function): + """ + double_swish(x) = x * torch.sigmoid(x-1) + This is a definition, originally motivated by its close numerical + similarity to swish(swish(x)), where swish(x) = x * sigmoid(x). + + Memory-efficient derivative computation: + double_swish(x) = x * s, where s(x) = torch.sigmoid(x-1) + double_swish'(x) = d/dx double_swish(x) = x * s'(x) + x' * s(x) = x * s'(x) + s(x). + Now, s'(x) = s(x) * (1-s(x)). + double_swish'(x) = x * s'(x) + s(x). + = x * s(x) * (1-s(x)) + s(x). + = double_swish(x) * (1-s(x)) + s(x) + ... so we just need to remember s(x) but not x itself. + """ + + @staticmethod + def forward(ctx, x: Tensor) -> Tensor: + requires_grad = x.requires_grad + x_dtype = x.dtype + if x.dtype == torch.float16: + x = x.to(torch.float32) + + s = torch.sigmoid(x - 1.0) + y = x * s + + if requires_grad: + deriv = y * (1 - s) + s + # notes on derivative of x * sigmoid(x - 1): + # https://www.wolframalpha.com/input?i=d%2Fdx+%28x+*+sigmoid%28x-1%29%29 + # min \simeq -0.043638. Take floor as -0.043637 so it's a lower bund + # max \simeq 1.1990. Take ceil to be 1.2 so it's an upper bound. + # the combination of "+ torch.rand_like(deriv)" and casting to torch.uint8 (which + # floors), should be expectation-preserving. + floor = -0.043637 + ceil = 1.2 + d_scaled = (deriv - floor) * ( + 255.0 / (ceil - floor) + ) + torch.rand_like(deriv) + if __name__ == "__main__": + # for self-testing only. + assert d_scaled.min() >= 0.0 + assert d_scaled.max() < 256.0 + d_int = d_scaled.to(torch.uint8) + ctx.save_for_backward(d_int) + if x.dtype == torch.float16 or torch.is_autocast_enabled(): + y = y.to(torch.float16) + return y + + @staticmethod + def backward(ctx, y_grad: Tensor) -> Tensor: + (d,) = ctx.saved_tensors + # the same constants as used in forward pass. + floor = -0.043637 + ceil = 1.2 + d = d * ((ceil - floor) / 255.0) + floor + return y_grad * d + + +class DoubleSwish(torch.nn.Module): + def forward(self, x: Tensor) -> Tensor: + """Return double-swish activation function which is an approximation to Swish(Swish(x)), + that we approximate closely with x * sigmoid(x-1). + """ + if torch.jit.is_scripting() or torch.jit.is_tracing(): + return x * torch.sigmoid(x - 1.0) + return DoubleSwishFunction.apply(x) + + +def BalancedDoubleSwish( + d_model, channel_dim=-1, max_abs=10.0, min_prob=0.25 +) -> nn.Sequential: + """ + ActivationBalancer -> DoubleSwish + """ + balancer = ActivationBalancer( + d_model, channel_dim=channel_dim, max_abs=max_abs, min_prob=min_prob + ) + return nn.Sequential( + balancer, + DoubleSwish(), + ) + + +def _test_max_eig(): + for proportion in [0.1, 0.5, 10.0]: + logging.info(f"proportion = {proportion}") + x = torch.randn(100, 128) + direction = torch.randn(128) + coeffs = torch.randn(100, 1) + x += proportion * direction * coeffs + + x.requires_grad = True + + num_channels = 128 + m = MaxEig( + num_channels, 1, 0.5, scale=0.1 # channel_dim # max_var_per_eig + ) # grad_scale + + for _ in range(4): + y = m(x) + + y_grad = torch.randn_like(x) + y.backward(gradient=y_grad) + + if proportion < 0.2: + assert torch.allclose(x.grad, y_grad, atol=1.0e-02) + elif proportion > 1.0: + assert not torch.allclose(x.grad, y_grad) + + +def _test_whiten(): + for proportion in [0.1, 0.5, 10.0]: + logging.info(f"_test_whiten(): proportion = {proportion}") + x = torch.randn(100, 128) + direction = torch.randn(128) + coeffs = torch.randn(100, 1) + x += proportion * direction * coeffs + + x.requires_grad = True + + num_channels = 128 + m = Whiten( + 1, 5.0, prob=1.0, grad_scale=0.1 # num_groups # whitening_limit, + ) # grad_scale + + for _ in range(4): + y = m(x) + + y_grad = torch.randn_like(x) + y.backward(gradient=y_grad) + + if proportion < 0.2: + assert torch.allclose(x.grad, y_grad) + elif proportion > 1.0: + assert not torch.allclose(x.grad, y_grad) + + +def _test_activation_balancer_sign(): + probs = torch.arange(0, 1, 0.01) + N = 1000 + x = 1.0 * ( + (2.0 * (torch.rand(probs.numel(), N) < probs.unsqueeze(-1))) - 1.0 + ) + x = x.detach() + x.requires_grad = True + m = ActivationBalancer( + probs.numel(), + channel_dim=0, + min_positive=0.05, + max_positive=0.95, + max_factor=0.2, + min_abs=0.0, + ) + + y_grad = torch.sign(torch.randn(probs.numel(), N)) + + y = m(x) + y.backward(gradient=y_grad) + print("_test_activation_balancer_sign: x = ", x) + print("_test_activation_balancer_sign: y grad = ", y_grad) + print("_test_activation_balancer_sign: x grad = ", x.grad) + + +def _test_activation_balancer_magnitude(): + magnitudes = torch.arange(0, 1, 0.01) + N = 1000 + x = torch.sign(torch.randn(magnitudes.numel(), N)) * magnitudes.unsqueeze( + -1 + ) + x = x.detach() + x.requires_grad = True + m = ActivationBalancer( + magnitudes.numel(), + channel_dim=0, + min_positive=0.0, + max_positive=1.0, + max_factor=0.2, + min_abs=0.2, + max_abs=0.8, + min_prob=1.0, + ) + + y_grad = torch.sign(torch.randn(magnitudes.numel(), N)) + + y = m(x) + y.backward(gradient=y_grad) + print("_test_activation_balancer_magnitude: x = ", x) + print("_test_activation_balancer_magnitude: y grad = ", y_grad) + print("_test_activation_balancer_magnitude: x grad = ", x.grad) + + +def _test_basic_norm(): + num_channels = 128 + m = BasicNorm(num_channels=num_channels, channel_dim=1) + + x = torch.randn(500, num_channels) + + y = m(x) + + assert y.shape == x.shape + x_rms = (x ** 2).mean().sqrt() + y_rms = (y ** 2).mean().sqrt() + print("x rms = ", x_rms) + print("y rms = ", y_rms) + assert y_rms < x_rms + assert y_rms > 0.5 * x_rms + + +def _test_double_swish_deriv(): + x = torch.randn(10, 12, dtype=torch.double) * 3.0 + x.requires_grad = True + m = DoubleSwish() + + tol = (1.2 - (-0.043637)) / 255.0 + torch.autograd.gradcheck(m, x, atol=tol) + + # for self-test. + x = torch.randn(1000, 1000, dtype=torch.double) * 3.0 + x.requires_grad = True + y = m(x) + + +def _test_softmax(): + a = torch.randn(2, 10, dtype=torch.float64) + b = a.clone() + a.requires_grad = True + b.requires_grad = True + a.softmax(dim=1)[:, 0].sum().backward() + print("a grad = ", a.grad) + softmax(b, dim=1)[:, 0].sum().backward() + print("b grad = ", b.grad) + assert torch.allclose(a.grad, b.grad) + + +if __name__ == "__main__": + logging.getLogger().setLevel(logging.INFO) + torch.set_num_threads(1) + torch.set_num_interop_threads(1) + _test_softmax() + _test_whiten() + _test_max_eig() + _test_activation_balancer_sign() + _test_activation_balancer_magnitude() + _test_basic_norm() + _test_double_swish_deriv() \ No newline at end of file diff --git a/models/modules/transformer.py b/models/modules/transformer.py new file mode 100644 index 0000000..1859258 --- /dev/null +++ b/models/modules/transformer.py @@ -0,0 +1,698 @@ +# cp from https://github.com/lifeiteng/vall-e/blob/main/valle/modules/transformer.py, modified by Puyuan Peng 2024 +import copy +import numbers +from functools import partial +from typing import Any, Callable, List, Optional, Tuple, Union + +import torch +from torch import Tensor, nn +from torch.nn import functional as F + +from .activation import MultiheadAttention +from .scaling import ActivationBalancer, BalancedDoubleSwish +from .scaling import BasicNorm as _BasicNorm + +_shape_t = Union[int, List[int], torch.Size] + + +class LayerNorm(nn.Module): + __constants__ = ["normalized_shape", "eps", "elementwise_affine"] + normalized_shape: Tuple[int, ...] + eps: float + elementwise_affine: bool + + def __init__( + self, + normalized_shape: _shape_t, + eps: float = 1e-5, + elementwise_affine: bool = True, + device=None, + dtype=None, + ) -> None: + factory_kwargs = {"device": device, "dtype": dtype} + super(LayerNorm, self).__init__() + if isinstance(normalized_shape, numbers.Integral): + # mypy error: incompatible types in assignment + normalized_shape = (normalized_shape,) # type: ignore[assignment] + self.normalized_shape = tuple(normalized_shape) # type: ignore[arg-type] + self.eps = eps + self.elementwise_affine = elementwise_affine + if self.elementwise_affine: + self.weight = nn.Parameter( + torch.empty(self.normalized_shape, **factory_kwargs) + ) + self.bias = nn.Parameter( + torch.empty(self.normalized_shape, **factory_kwargs) + ) + else: + self.register_parameter("weight", None) + self.register_parameter("bias", None) + + self.reset_parameters() + + def reset_parameters(self) -> None: + if self.elementwise_affine: + nn.init.ones_(self.weight) + nn.init.zeros_(self.bias) + + def forward(self, input: Tensor, embedding: Any = None) -> Tensor: + if isinstance(input, tuple): + input, embedding = input + return ( + F.layer_norm( + input, + self.normalized_shape, + self.weight, + self.bias, + self.eps, + ), + embedding, + ) + + assert embedding is None + return F.layer_norm( + input, self.normalized_shape, self.weight, self.bias, self.eps + ) + + def extra_repr(self) -> str: + return ( + "{normalized_shape}, eps={eps}, " + "elementwise_affine={elementwise_affine}".format(**self.__dict__) + ) + + +class AdaptiveLayerNorm(nn.Module): + r"""Adaptive Layer Normalization""" + + def __init__(self, d_model, norm) -> None: + super(AdaptiveLayerNorm, self).__init__() + self.project_layer = nn.Linear(d_model, 2 * d_model) + self.norm = norm + self.d_model = d_model + self.eps = self.norm.eps + + def forward(self, input: Tensor, embedding: Tensor = None) -> Tensor: + if isinstance(input, tuple): + input, embedding = input + weight, bias = torch.split( + self.project_layer(embedding), + split_size_or_sections=self.d_model, + dim=-1, + ) + return (weight * self.norm(input) + bias, embedding) + + weight, bias = torch.split( + self.project_layer(embedding), + split_size_or_sections=self.d_model, + dim=-1, + ) + return weight * self.norm(input) + bias + + +class BasicNorm(_BasicNorm): + def __init__( + self, + d_model: int, + eps: float = 1e-5, + device=None, + dtype=None, + ): + super(BasicNorm, self).__init__(d_model, eps=eps) + + def forward(self, input: Tensor, embedding: Any = None) -> Tensor: + if isinstance(input, tuple): + input, embedding = input + return ( + super(BasicNorm, self).forward(input), + embedding, + ) + + assert embedding is None + return super(BasicNorm, self).forward(input) + + +class BalancedBasicNorm(nn.Module): + def __init__( + self, + d_model: int, + eps: float = 1e-5, + device=None, + dtype=None, + ): + super(BalancedBasicNorm, self).__init__() + self.balancer = ActivationBalancer( + d_model, + channel_dim=-1, + min_positive=0.45, + max_positive=0.55, + max_abs=6.0, + ) + self.norm = BasicNorm(d_model, eps, device=device, dtype=dtype) + + def forward(self, input: Tensor, embedding: Any = None) -> Tensor: + if isinstance(input, tuple): + input, embedding = input + return self.norm((self.balancer(input), embedding)) + + assert embedding is None + return self.norm(self.balancer(input)) + + +class IdentityNorm(nn.Module): + def __init__( + self, + d_model: int, + eps: float = 1e-5, + device=None, + dtype=None, + ) -> None: + super(IdentityNorm, self).__init__() + + def forward(self, input: Tensor, embedding: Any = None) -> Tensor: + if isinstance(input, tuple): + return input + + assert embedding is None + return input + + +class TransformerEncoderLayer(nn.Module): + __constants__ = ["batch_first", "norm_first"] + + def __init__( + self, + d_model: int, + nhead: int, + dim_feedforward: int = 2048, + dropout: float = 0.1, + activation: Union[str, Callable[[Tensor], Tensor]] = F.relu, + batch_first: bool = False, + norm_first: bool = False, + device=None, + dtype=None, + linear1_self_attention_cls: nn.Module = nn.Linear, + linear2_self_attention_cls: nn.Module = nn.Linear, + linear1_feedforward_cls: nn.Module = nn.Linear, + linear2_feedforward_cls: nn.Module = nn.Linear, + layer_norm_cls: nn.Module = LayerNorm, + layer_norm_eps: float = 1e-5, + adaptive_layer_norm=False, + ) -> None: + factory_kwargs = {"device": device, "dtype": dtype} + super(TransformerEncoderLayer, self).__init__() + self.self_attn = MultiheadAttention( + d_model, + nhead, + dropout=dropout, + batch_first=batch_first, + linear1_cls=linear1_self_attention_cls, + linear2_cls=linear2_self_attention_cls, + **factory_kwargs, + ) + + # Implementation of Feedforward model + self.linear1 = linear1_feedforward_cls( + d_model, dim_feedforward, **factory_kwargs + ) + self.dropout = nn.Dropout(dropout) + self.linear2 = linear2_feedforward_cls( + dim_feedforward, d_model, **factory_kwargs + ) + + self.norm_first = norm_first + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + + # Legacy string support for activation function. + if isinstance(activation, str): + activation = _get_activation_fn(activation) + elif isinstance(activation, partial): + activation = activation(d_model) + elif activation == BalancedDoubleSwish: + activation = BalancedDoubleSwish(d_model) + + # # We can't test self.activation in forward() in TorchScript, + # # so stash some information about it instead. + # if activation is F.relu or isinstance(activation, torch.nn.ReLU): + # self.activation_relu_or_gelu = 1 + # elif activation is F.gelu or isinstance(activation, torch.nn.GELU): + # self.activation_relu_or_gelu = 2 + # else: + # self.activation_relu_or_gelu = 0 + self.activation = activation + + norm1 = layer_norm_cls(d_model, eps=layer_norm_eps, **factory_kwargs) + if layer_norm_cls == IdentityNorm: + norm2 = BalancedBasicNorm( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + else: + norm2 = layer_norm_cls( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + + if adaptive_layer_norm: + self.norm1 = AdaptiveLayerNorm(d_model, norm1) + self.norm2 = AdaptiveLayerNorm(d_model, norm2) + else: + self.norm1 = norm1 + self.norm2 = norm2 + + def __setstate__(self, state): + super(TransformerEncoderLayer, self).__setstate__(state) + if not hasattr(self, "activation"): + self.activation = F.relu + + def forward( + self, + src: Tensor, + src_mask: Optional[Tensor] = None, + src_key_padding_mask: Optional[Tensor] = None, + need_weights: Optional[bool] = False, + past: Optional[Tensor] = None, + ) -> Tensor: + r"""Pass the input through the encoder layer. + + Args: + src: the sequence to the encoder layer (required). + src_mask: the mask for the src sequence (optional). + src_key_padding_mask: the mask for the src keys per batch (optional). + + Shape: + see the docs in Transformer class. + """ + x, stage_embedding = src, None + is_src_tuple = False + if isinstance(src, tuple): + x, stage_embedding = src + is_src_tuple = True + + if src_key_padding_mask is not None: + _skpm_dtype = src_key_padding_mask.dtype + if _skpm_dtype != torch.bool and not torch.is_floating_point( + src_key_padding_mask + ): + raise AssertionError( + "only bool and floating types of key_padding_mask are supported" + ) + if need_weights: + if self.norm_first: + out, attn = self._sa_block_attn( + self.norm1(x, stage_embedding), + src_mask, + src_key_padding_mask, + past + ) + out, present = out # present is the kvcache of the present timestep + x = x + out + x = x + self._ff_block(self.norm2(x, stage_embedding)) + else: + out, attn = self._sa_block_attn(x, src_mask, src_key_padding_mask, past) + out, present = out # present is the kvcache of the present timestep + x = self.norm1( + x + out, + stage_embedding, + ) + x = self.norm2(x + self._ff_block(x), stage_embedding) + assert not is_src_tuple + # return (x, stage_embedding) + return (x, attn) + else: + if self.norm_first: + out = self._sa_block( + self.norm1(x, stage_embedding), + src_mask, + src_key_padding_mask, past + ) + out, present = out # present is the kvcache of the present timestep + x = x + out + x = x + self._ff_block(self.norm2(x, stage_embedding)) + else: + out = self._sa_block(x, src_mask, src_key_padding_mask) + out, present = out # present is the kvcache of the present timestep + x = self.norm1( + x + out, + stage_embedding, past + ) + x = self.norm2(x + self._ff_block(x), stage_embedding) + + if is_src_tuple: + x = (x, stage_embedding) + if present != None: + x = [x, present] + return x + + # self-attention block + def _sa_block( + self, + x: Tensor, + attn_mask: Optional[Tensor], + key_padding_mask: Optional[Tensor], + past: Optional[Tensor] = None, + ) -> Tensor: + x = self.self_attn( + x, + x, + x, + attn_mask=attn_mask, + key_padding_mask=key_padding_mask, + need_weights=False, + past=past + ) + x, present = x + return self.dropout1(x), present + + # self-attention block, also return attention weights + def _sa_block_attn( + self, + x: Tensor, + attn_mask: Optional[Tensor], + key_padding_mask: Optional[Tensor], + past: Optional[Tensor] = None, + ) -> Tensor: + x, attn = self.self_attn( + x, + x, + x, + attn_mask=attn_mask, + key_padding_mask=key_padding_mask, + need_weights=True, + past=past + ) + x, present = x + return (self.dropout1(x), present), attn + + # feed forward block + def _ff_block(self, x: Tensor) -> Tensor: + x = self.linear2(self.dropout(self.activation(self.linear1(x)))) + return self.dropout2(x) + + +class TransformerEncoder(nn.Module): + r"""TransformerEncoder is a stack of N encoder layers. Users can build the + BERT(https://arxiv.org/abs/1810.04805) model with corresponding parameters. + + Args: + encoder_layer: an instance of the TransformerEncoderLayer() class (required). + num_layers: the number of sub-encoder-layers in the encoder (required). + norm: the layer normalization component (optional). + enable_nested_tensor: if True, input will automatically convert to nested tensor + (and convert back on output). This will improve the overall performance of + TransformerEncoder when padding rate is high. Default: ``True`` (enabled). + + Examples:: + >>> encoder_layer = TransformerEncoderLayer(d_model=512, nhead=8) + >>> transformer_encoder = TransformerEncoder(encoder_layer, num_layers=6) + >>> src = torch.rand(10, 32, 512) + >>> out = transformer_encoder(src) + """ + __constants__ = ["norm"] + + def __init__(self, encoder_layer, num_layers, norm=None): + super(TransformerEncoder, self).__init__() + self.layers = _get_clones(encoder_layer, num_layers) + self.num_layers = num_layers + self.norm = norm + + def forward( + self, + src: Tensor, + mask: Optional[Tensor] = None, + src_key_padding_mask: Optional[Tensor] = None, + return_layer_states: bool = False, + need_weights:Optional[bool] = False, + past: Optional[Tensor] = None, + ) -> Tensor: + r"""Pass the input through the encoder layers in turn. + + Args: + src: the sequence to the encoder (required). + mask: the mask for the src sequence (optional). + src_key_padding_mask: the mask for the src keys per batch (optional). + return_layer_states: return layers' state (optional). + + Shape: + see the docs in Transformer class. + """ + if return_layer_states: + assert not need_weights + layer_states = [] # layers' output + output = src + for mod in self.layers: + output = mod( + output, + src_mask=mask, + src_key_padding_mask=src_key_padding_mask, + past=past + ) + layer_states.append(output[0]) + + if self.norm is not None: + output = self.norm(output) + + return layer_states, output + if need_weights: + assert not return_layer_states + layer_attn = [] # layers' output + output = src + for mod in self.layers: + output = mod( + output, + src_mask=mask, + src_key_padding_mask=src_key_padding_mask, + need_weights=True, + past=past + ) + layer_attn.append(output[1]) + + if self.norm is not None: + output = self.norm(output) + + return layer_attn, output + + output = src + all_present = [] + for n_layer, mod in enumerate(self.layers): + output = mod( + output, src_mask=mask, src_key_padding_mask=src_key_padding_mask, past=None if past is None else past[n_layer] + ) + if isinstance(output, list): + output, present = output + all_present.append(present) + + if self.norm is not None: + output = self.norm(output) + if all_present != []: + all_present = torch.stack(all_present, dim=0) # (num_layers, 2, batch_size, num_heads, seq_len, head_dim) + output = [output, all_present] + return output + + +class TransformerDecoderLayer(nn.Module): + __constants__ = ["batch_first", "norm_first"] + + def __init__( + self, + d_model: int, + nhead: int, + dim_feedforward: int = 2048, + dropout: float = 0.1, + activation: Union[str, Callable[[Tensor], Tensor]] = F.relu, + linear1_self_attention_cls: nn.Module = nn.Linear, + linear2_self_attention_cls: nn.Module = nn.Linear, + linear1_feedforward_cls: nn.Module = nn.Linear, + linear2_feedforward_cls: nn.Module = nn.Linear, + batch_first: bool = False, + norm_first: bool = False, + device=None, + dtype=None, + layer_norm_cls: nn.Module = LayerNorm, + layer_norm_eps: float = 1e-5, + adaptive_layer_norm=False, + ) -> None: + factory_kwargs = {"device": device, "dtype": dtype} + super(TransformerDecoderLayer, self).__init__() + self.self_attn = MultiheadAttention( + d_model, + nhead, + dropout=dropout, + batch_first=batch_first, + linear1_cls=linear1_self_attention_cls, + linear2_cls=linear2_self_attention_cls, + **factory_kwargs, + ) + self.multihead_attn = MultiheadAttention( + d_model, + nhead, + dropout=dropout, + batch_first=batch_first, + linear1_cls=linear1_self_attention_cls, + linear2_cls=linear2_self_attention_cls, + **factory_kwargs, + ) + # Implementation of Feedforward model + self.linear1 = linear1_feedforward_cls( + d_model, dim_feedforward, **factory_kwargs + ) + self.dropout = nn.Dropout(dropout) + self.linear2 = linear2_feedforward_cls( + dim_feedforward, d_model, **factory_kwargs + ) + + self.norm_first = norm_first + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + self.dropout3 = nn.Dropout(dropout) + + # Legacy string support for activation function. + if isinstance(activation, str): + self.activation = _get_activation_fn(activation) + elif isinstance(activation, partial): + self.activation = activation(d_model) + elif activation == BalancedDoubleSwish: + self.activation = BalancedDoubleSwish(d_model) + else: + self.activation = activation + + if adaptive_layer_norm: + norm1 = layer_norm_cls( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + norm2 = layer_norm_cls( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + norm3 = layer_norm_cls( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + + self.norm1 = AdaptiveLayerNorm(d_model, norm1) + self.norm2 = AdaptiveLayerNorm(d_model, norm2) + self.norm3 = AdaptiveLayerNorm(d_model, norm3) + else: + self.norm1 = layer_norm_cls( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + self.norm2 = layer_norm_cls( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + if layer_norm_cls == IdentityNorm: + self.norm3 = BalancedBasicNorm( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + else: + self.norm3 = layer_norm_cls( + d_model, eps=layer_norm_eps, **factory_kwargs + ) + + def forward( + self, + tgt: Tensor, + memory: Tensor, + tgt_mask: Optional[Tensor] = None, + memory_mask: Optional[Tensor] = None, + tgt_key_padding_mask: Optional[Tensor] = None, + memory_key_padding_mask: Optional[Tensor] = None, + ) -> Tensor: + r"""Pass the inputs (and mask) through the decoder layer. + + Args: + tgt: the sequence to the decoder layer (required). + memory: the sequence from the last layer of the encoder (required). + tgt_mask: the mask for the tgt sequence (optional). + memory_mask: the mask for the memory sequence (optional). + tgt_key_padding_mask: the mask for the tgt keys per batch (optional). + memory_key_padding_mask: the mask for the memory keys per batch (optional). + + Shape: + see the docs in Transformer class. + """ + tgt_is_tuple = False + if isinstance(tgt, tuple): + x, stage_embedding = tgt + tgt_is_tuple = True + else: + x, stage_embedding = tgt, None + + if self.norm_first: + x = x + self._sa_block( + self.norm1(x, stage_embedding), tgt_mask, tgt_key_padding_mask + ) + x = x + self._mha_block( + self.norm2(x, stage_embedding), + memory, + memory_mask, + memory_key_padding_mask, + ) + x = x + self._ff_block(self.norm3(x, stage_embedding)) + else: + x = self.norm1( + x + self._sa_block(x, tgt_mask, tgt_key_padding_mask), + stage_embedding, + ) + x = self.norm2( + x + + self._mha_block( + x, memory, memory_mask, memory_key_padding_mask + ), + stage_embedding, + ) + x = self.norm3(x + self._ff_block(x), stage_embedding) + + if tgt_is_tuple: + return (x, stage_embedding) + return x + + # self-attention block + def _sa_block( + self, + x: Tensor, + attn_mask: Optional[Tensor], + key_padding_mask: Optional[Tensor], + ) -> Tensor: + x = self.self_attn( + x, + x, + x, + attn_mask=attn_mask, + key_padding_mask=key_padding_mask, + need_weights=False, + )[0] + return self.dropout1(x) + + # multihead attention block + def _mha_block( + self, + x: Tensor, + mem: Tensor, + attn_mask: Optional[Tensor], + key_padding_mask: Optional[Tensor], + ) -> Tensor: + x = self.multihead_attn( + x, + mem, + mem, + attn_mask=attn_mask, + key_padding_mask=key_padding_mask, + need_weights=False, + )[0] + return self.dropout2(x) + + # feed forward block + def _ff_block(self, x: Tensor) -> Tensor: + x = self.linear2(self.dropout(self.activation(self.linear1(x)))) + return self.dropout3(x) + + +def _get_clones(module, N): + return nn.ModuleList([copy.deepcopy(module) for i in range(N)]) + + +def _get_activation_fn(activation: str) -> Callable[[Tensor], Tensor]: + if activation == "relu": + return F.relu + elif activation == "gelu": + return F.gelu + + raise RuntimeError( + "activation should be relu/gelu, not {}".format(activation) + ) \ No newline at end of file diff --git a/models/modules/utils.py b/models/modules/utils.py new file mode 100644 index 0000000..8a46980 --- /dev/null +++ b/models/modules/utils.py @@ -0,0 +1,37 @@ +# cp from https://github.com/lifeiteng/vall-e/blob/main/valle/modules/transformer.py, modified by Puyuan Peng +import torch + + +def make_pad_mask(lengths: torch.Tensor, max_len: int = 0) -> torch.Tensor: + """ + Args: + lengths: + A 1-D tensor containing sentence lengths. + max_len: + The length of masks. + Returns: + Return a 2-D bool tensor, where masked positions + are filled with `True` and non-masked positions are + filled with `False`. + + >>> lengths = torch.tensor([1, 3, 2, 5]) + >>> make_pad_mask(lengths) + tensor([[False, True, True, True, True], + [False, False, False, True, True], + [False, False, True, True, True], + [False, False, False, False, False]]) + """ + assert lengths.ndim == 1, lengths.ndim + max_len = max(max_len, lengths.max()) + n = lengths.size(0) + seq_range = torch.arange(0, max_len, device=lengths.device) + expaned_lengths = seq_range.unsqueeze(0).expand(n, max_len) + + return expaned_lengths >= lengths.unsqueeze(-1) + +def generate_partial_autoregressive_mask(sz, start, end): + mask = torch.zeros(sz, sz).bool() + mask[start:end, start:end] = torch.triu(torch.ones(end-start, end-start,dtype=torch.bool), diagonal=1) + mask[:start, start:end] = True + mask[end:, start:end] = True + return mask diff --git a/models/voicecraft.py b/models/voicecraft.py new file mode 100644 index 0000000..4042cae --- /dev/null +++ b/models/voicecraft.py @@ -0,0 +1,1402 @@ +import random + +import numpy as np +import logging +import argparse, copy +import torch +import torch.nn as nn +import torch.nn.functional as F +from torchmetrics.classification import MulticlassAccuracy + +from .modules.utils import make_pad_mask + +from .modules.embedding import SinePositionalEmbedding, TokenEmbedding +from .modules.transformer import ( + LayerNorm, + TransformerEncoder, + TransformerEncoderLayer, +) +from .codebooks_patterns import DelayedPatternProvider + +def top_k_top_p_filtering( + logits, top_k=0, top_p=1.0, filter_value=-float("Inf"), min_tokens_to_keep=1 +): + """Filter a distribution of logits using top-k and/or nucleus (top-p) filtering + Args: + logits: logits distribution shape (batch size, vocabulary size) + if top_k > 0: keep only top k tokens with highest probability (top-k filtering). + if top_p < 1.0: keep the top tokens with cumulative probability >= top_p (nucleus filtering). + Nucleus filtering is described in Holtzman et al. (http://arxiv.org/abs/1904.09751) + Make sure we keep at least min_tokens_to_keep per batch example in the output + From: https://gist.github.com/thomwolf/1a5a29f6962089e871b94cbd09daf317 + """ + if top_k > 0: + top_k = min( + max(top_k, min_tokens_to_keep), logits.size(-1) + ) # Safety check + # Remove all tokens with a probability less than the last token of the top-k + indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] + logits[indices_to_remove] = filter_value + + if top_p < 1.0: + sorted_logits, sorted_indices = torch.sort(logits, descending=True) + cumulative_probs = torch.cumsum( + F.softmax(sorted_logits, dim=-1), dim=-1 + ) + + # Remove tokens with cumulative probability above the threshold (token with 0 are kept) + sorted_indices_to_remove = cumulative_probs > top_p + if min_tokens_to_keep > 1: + # Keep at least min_tokens_to_keep (set to min_tokens_to_keep-1 because we add the first one below) + sorted_indices_to_remove[..., :min_tokens_to_keep] = 0 + # Shift the indices to the right to keep also the first token above the threshold + sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[ + ..., :-1 + ].clone() + sorted_indices_to_remove[..., 0] = 0 + + # scatter sorted tensors to original indexing + indices_to_remove = sorted_indices_to_remove.scatter( + 1, sorted_indices, sorted_indices_to_remove + ) + logits[indices_to_remove] = filter_value + return logits + + +def topk_sampling(logits, top_k=10, top_p=1.0, temperature=1.0): + # temperature: (`optional`) float + # The value used to module the next token probabilities. Must be strictly positive. Default to 1.0. + # top_k: (`optional`) int + # The number of highest probability vocabulary tokens to keep for top-k-filtering. Between 1 and infinity. Default to 50. + # top_p: (`optional`) float + # The cumulative probability of parameter highest probability vocabulary tokens to keep for nucleus sampling. Must be between 0 and 1. Default to 1. + + # Temperature (higher temperature => more likely to sample low probability tokens) + if temperature != 1.0: + logits = logits / temperature + # Top-p/top-k filtering + logits = top_k_top_p_filtering(logits, top_k=top_k, top_p=top_p) + # Sample + token = torch.multinomial(F.softmax(logits, dim=-1), num_samples=1) + return token + + + +class VoiceCraft(nn.Module): + def __init__(self, args): + super().__init__() + self.args = copy.copy(args) + self.pattern = DelayedPatternProvider(n_q=self.args.n_codebooks) + if not getattr(self.args, "special_first", False): + self.args.special_first = 0 + if not getattr(self.args, "n_special", False): + self.args.n_special = 3 + self.args.eos = getattr(self.args, "eos", -1) + self.eog = nn.Parameter(torch.full((self.args.n_codebooks, 1), self.args.eog, dtype=torch.long), requires_grad=False) # [K 1] + if self.args.eos > 0: + assert self.args.eos != self.args.audio_pad_token and self.args.eos != self.args.empty_token, self.args.eos + self.eos = nn.Parameter(torch.full((self.args.n_codebooks, 1), self.args.eos, dtype=torch.long), requires_grad=False) # [K 1] + if type(self.args.audio_vocab_size) == str: + self.args.audio_vocab_size = eval(self.args.audio_vocab_size) + + self.n_text_tokens = self.args.text_vocab_size + 1 + assert self.args.text_pad_token == self.args.text_vocab_size, f"self.args.text_vocab_size: {self.args.text_vocab_size}, self.args.text_pad_token: {self.args.text_pad_token}" + + self.n_audio_tokens = [self.args.audio_vocab_size + self.args.n_special] * self.args.n_codebooks # special tokens: empty token, EOG token, audio pad token + assert self.args.audio_vocab_size == self.args.empty_token, self.args.empty_token + assert self.args.eog == self.args.audio_vocab_size + 1, self.args.eog + assert self.args.audio_pad_token == self.args.audio_vocab_size + 2, self.args.audio_pad_token + + self.text_embedding = TokenEmbedding( + dim_model=self.args.d_model, + vocab_size=self.n_text_tokens, + dropout=self.args.text_embedding_dropout + ) + + self.audio_embedding = nn.ModuleList( + [ + TokenEmbedding( + dim_model=self.args.audio_embedding_dim, + vocab_size=self.n_audio_tokens[k], + dropout=self.args.audio_embedding_dropout + ) for k in range(self.args.n_codebooks) + ] + ) + self.mask_embedding = nn.Parameter(torch.randn(self.args.max_n_spans, self.args.d_model), requires_grad=True) + self.text_positional_embedding = SinePositionalEmbedding( + self.args.d_model, + dropout=self.args.text_positional_embedding_dropout, + scale=False, + alpha=True, # learnable scaler, scale the volume of positional embedding + ) + self.audio_positional_embedding = SinePositionalEmbedding( + self.args.d_model, + dropout=self.args.audio_positional_embedding_dropout, + scale=False, + alpha=True, # learnable scaler, scale the volume of positional embedding + ) + + dec_layer = TransformerEncoderLayer( + self.args.d_model, + self.args.nhead, + dim_feedforward=self.args.d_model * 4, + dropout=self.args.trm_dropout, + batch_first=True, + norm_first=True, + layer_norm_cls=LayerNorm + ) + self.decoder = TransformerEncoder( + dec_layer, + num_layers=self.args.num_decoder_layers, + norm=LayerNorm(self.args.d_model), + ) + + self.predict_layer = nn.ModuleList( + [ + nn.Sequential(nn.Linear(self.args.d_model, self.args.audio_vocab_size//2), nn.GELU(), nn.Linear(self.args.audio_vocab_size//2, self.n_audio_tokens[k])) for k in range(self.args.n_codebooks) + ] + ) + + self.accuracy_metrics = nn.ModuleList( + [MulticlassAccuracy( + self.n_audio_tokens[k], + top_k=10, + average="micro", + multidim_average="global", + ignore_index=None, + ) for k in range(self.args.n_codebooks)] + ) + + + def prepare_mask_intervals(self, y_lens): + mask_intervals = [] + non_mask_intervals = [] + + for i, y_len in enumerate(y_lens): + if self.args.mask_sample_dist == "uniform": + n_spans = random.choice(range(1, self.args.max_n_spans+1)) + elif "poisson" in self.args.mask_sample_dist.lower(): + param = float(self.args.mask_sample_dist[len("poisson"):]) + poisson_sample = torch.poisson(torch.tensor([param])) + n_spans = int(poisson_sample.clamp(1, self.args.max_n_spans).item()) + + starts = random.sample(range(1, y_len-1-self.args.mask_len_min), n_spans) + starts = sorted(starts) + + for j in range(len(starts)-1, 0, -1): + if starts[j] - starts[j-1] < self.args.min_gap: + del starts[j] # If elements are too close, delete the later one + assert len(starts) > 0, f"there is no masked span left, y_len: {y_len}, sampled n_spans: {n_spans}" + + temp_starts = starts + [y_len] + gaps = [temp_starts[j+1] - temp_starts[j] for j in range(len(temp_starts)-1)] + + ends = [] + + for j, (start, gap) in enumerate(zip(starts, gaps)): + mask_len = random.randint(self.args.mask_len_min, self.args.mask_len_max) + # if mask_len > gap * self.args.max_mask_portion: # make sure the masks are not overlapping with each other + if mask_len > gap - 1: # make sure the masks are not overlapping with each other + # temp_mask_start = int(0.6*gap*self.args.max_mask_portion) + # temp_mask_end = int(gap*self.args.max_mask_portion) + temp_mask_start = 1 + temp_mask_end = gap - 1 + mask_len = random.randint(temp_mask_start, temp_mask_end) + ends.append(start + mask_len) + + mask_intervals.append([(s,e) for s,e in zip(starts, ends)]) + non_mask_intervals.append([(ns,ne) for ns, ne in zip([0]+ends, starts+[y_len])]) + + return mask_intervals, non_mask_intervals + + def rearrange(self, y, non_mask_intervals, mask_intervals): + reduced_eog = getattr(self.args, "reduced_eog", 0) + rearranged_y = [] + for i in range(len(y)): + if self.args.eos > 0: + assert reduced_eog + cur_y = [y[i, :, item[0]: item[1]] for item in non_mask_intervals[i][:-1]] + [torch.cat([y[i, :, non_mask_intervals[i][-1][0]: non_mask_intervals[i][-1][1]], self.eos], dim=-1)] + [torch.cat([y[i, :, item[0]: item[1]], self.eog], dim=-1) for item in mask_intervals[i]] # only insert eog to the last non-mask-interval, which is when the utterance actual ends + else: + if reduced_eog: + cur_y = [y[i, :, item[0]: item[1]] for item in non_mask_intervals[i][:-1]] + [torch.cat([y[i, :, non_mask_intervals[i][-1][0]: non_mask_intervals[i][-1][1]], self.eog], dim=-1)] + [torch.cat([y[i, :, item[0]: item[1]], self.eog], dim=-1) for item in mask_intervals[i]] # only insert eog to the last non-mask-interval, which is when the utterance actual ends + else: + cur_y = [torch.cat([y[i, :, item[0]: item[1]], self.eog], dim=-1) for item in non_mask_intervals[i]] + [torch.cat([y[i, :, item[0]: item[1]], self.eog], dim=-1) for item in mask_intervals[i]] # eog is added to each section TODO this is not correct, I should add eog to non_mask_intervals if that segment is not the ending segment (as there is no way for the model to predict eog for those segments, and this will do harm to tts experiment, where the model randomly output eog for the first segment) + rearranged_y.append(cur_y) + return rearranged_y + + def shift(self, rearranged_y): + shifted_y = [] + patterns = [] + for i in range(len(rearranged_y)): + cur_patterns = [self.pattern.get_pattern(cur_y.shape[1]) for cur_y in rearranged_y[i]] + out = [cur_pattern.build_pattern_sequence(z=cur_y.unsqueeze(0).contiguous(), special_token=self.args.empty_token, keep_only_valid_steps=False) for cur_pattern, cur_y in zip(cur_patterns, rearranged_y[i])] + shifted_y.append([item[0].squeeze(0) for item in out]) # the first item is values, later two are indexes and mask + patterns.append(cur_patterns) + return shifted_y, patterns + + def insert_mask(self, shifted_y): + inserted_y = [] + mask_position = [] + mask_value = [] + for i in range(len(shifted_y)): + num_masks = (len(shifted_y[i]) - 1) // 2 + assert num_masks == (len(shifted_y[i]) - 1) / 2, len(shifted_y[i]) + emb_inds = list(range(self.args.max_n_spans)) + if self.args.shuffle_mask_embedding: + random.shuffle(emb_inds) + emb_inds_use = emb_inds[:num_masks] + emb_inds_use = emb_inds_use + emb_inds_use + mask_value.append(emb_inds_use) + cur_inserted_y = [] + cur_mask_position = [] + for j in range(len(shifted_y[i])-1): + cur_inserted_y.append(shifted_y[i][j]) + cur_mask_position.append(sum([item.shape[1] for item in cur_inserted_y])) # each item is of shape [K S], so take shape[1] + cur_inserted_y.append(self.eog) # insert mask token of shape [K, 1], BUT we are actually using the eog token as a place holder here, as the real mask will be inserted in embed_y function + + cur_inserted_y.append(shifted_y[i][-1]) + + inserted_y.append(cur_inserted_y) + mask_position.append(cur_mask_position) + return inserted_y, mask_position, mask_value + + def cat_y(self, inserted_y, mask_position, y_lens): + reduced_eog = getattr(self.args, "reduced_eog", 0) + cated_y = [] + new_y_lens = [] + for i in range(len(inserted_y)): + cur_cated_y = torch.cat(inserted_y[i], dim=1) #[K S] + cur_cated_y = cur_cated_y.transpose(1,0) # [S K] + cur_cated_y_len = cur_cated_y.shape[0] + if reduced_eog: + assert cur_cated_y_len == y_lens[i] + len(mask_position[i]) + (len(mask_position[i]) + 1) * self.args.n_codebooks + (len(mask_position[i])/2 + 1), f"cur_cated_y_len == {cur_cated_y_len}, but it should be y_lens[i] ({y_lens[i]}) + len(mask_position[i]) ({len(mask_position[i])}) + (len(mask_position[i]) + 1) * self.args.n_codebooks ({(len(mask_position[i]) + 1) * self.args.n_codebooks}) + (len(mask_position[i])/2 + 1) ({len(mask_position[i])/2 + 1})={y_lens[i] + len(mask_position[i]) + (len(mask_position[i]) + 1) * self.args.n_codebooks + (len(mask_position[i])/2 + 1)}" + else: + assert cur_cated_y_len == y_lens[i] + len(mask_position[i]) + (len(mask_position[i]) + 1) * self.args.n_codebooks + (len(mask_position[i]) + 1), f"cur_cated_y_len == {cur_cated_y_len}, but it should be y_lens[i] ({y_lens[i]}) + len(mask_position[i]) ({len(mask_position[i])}) + (len(mask_position[i]) + 1) * self.args.n_codebooks ({(len(mask_position[i]) + 1) * self.args.n_codebooks}) + (len(mask_position[i]) + 1) ({len(mask_position[i]) + 1})" # the last term represent the inserted eog token, originally it's inserted at the end of every token, but this is wrong + new_y_lens.append(cur_cated_y_len) + cated_y.append(cur_cated_y) + + cated_y = torch.nn.utils.rnn.pad_sequence(cated_y, batch_first=False, padding_value=self.args.audio_pad_token) + assert cated_y.shape == torch.Size([max(new_y_lens),len(inserted_y), self.args.n_codebooks]), f"cated_y.shape: {cated_y.shape}, but it should be {torch.Size([max(new_y_lens,len(inserted_y), self.args.n_codebooks)])}" + cated_y = cated_y.permute(2,0,1) # [T,B,K]->[K,T,B] + assert cated_y.shape[0] == self.args.n_codebooks, cated_y.shape + return cated_y, torch.LongTensor(new_y_lens).to(cated_y.device) + + def embed_y(self, cated_y, mask_position, mask_value): + embedded_y = torch.stack([self.audio_embedding[k](cated_y[k]) for k in range(self.args.n_codebooks)], dim=0) # [K, T, B, D] + assert embedded_y.shape[0] == self.args.n_codebooks, embedded_y.shape + assert embedded_y.shape[-1] == self.args.d_model, embedded_y.shape + embedded_y = embedded_y.sum(dim=0) # [K,T,B,D]->[T,B,D] + embedded_y = embedded_y.transpose(1,0) # [T,B,D]->[B,T,D] + for i in range(len(embedded_y)): + if len(mask_position[i]) > 0: + embedded_y[i, mask_position[i]] = self.mask_embedding[mask_value[i]] + return embedded_y + + def prepare_input_target(self, y, y_lens): + # rearrange y + # assume y shape: [B T K], K is n_codebooks + assert y.shape[1] == self.args.n_codebooks, y.shape + # sample mask_intervals + mask_intervals, non_mask_intervals = self.prepare_mask_intervals(y_lens) + + # need to have EOG in each section (SOG will be generated by the pattern class) + # but mask can be inserted later after we have shifted the input + # y could be rearranged in this way: + # [ + # [tensor[4, 12], tensor[4, 45], tensor[4, 102], tensor[4, 32]], tensor[4, 22]], + # [tensor[4, 44], tensor[4, 56], tensor[4, 19]], + # ... + # ] + # for the first list of tensors (4 tensors), first 3 tensors are non_masked part, last 2 are masked part. + # NOTE #non_masked_part = #masked_part + 1 + # NOTE *these are also the targets* + # added eog at the end of each segment (masked segment and unmasked segment) + rearranged_y = self.rearrange(y, non_mask_intervals, mask_intervals) + targets = rearranged_y # each element in each sample is of shape [K T] + assert targets[0][0].shape[0] == self.args.n_codebooks, targets[0][0].shape + + # next we need to apply pattern shifting to each tensor, after which, we'll replace the starting tokens of each section with a token that's different from the special padding token + # [[5, 1, 2, 3, 4, 5, 5], + # [5, 5, 1, 2, 3, 4, 5], + # [5, 5, 5, 1, 2, 3, 4]] + shifted_y, patterns = self.shift(rearranged_y) # each element [K S] + assert shifted_y[0][0].shape[0] == self.args.n_codebooks, shifted_y[0][0].shape[0] + + + # then, insert mask token at the intersection of each tensor (we want to decide the arrangement of the mask (shuffle or not)), we better have a separate nn.embedding for it + # we also need to record the position of the inserted mask + inserted_y, mask_position, mask_value = self.insert_mask(shifted_y) + assert inserted_y[0][0].shape[0] == self.args.n_codebooks, inserted_y[0][0].shape[0] + assert inserted_y[0][1].shape == torch.Size((self.args.n_codebooks, 1)), f"this should be a mask, so should have shape {(self.args.n_codebooks, 1)}, but it's {inserted_y[0][1].shape}" + + # then concat tensors that belong to the same sample (in order) then get the length of each sample, and then stack them in batch dimension, pad them with pad_token + cated_y, new_y_lens = self.cat_y(inserted_y, mask_position, y_lens) # KTB + assert cated_y.shape == torch.Size((self.args.n_codebooks, cated_y.shape[1], len(inserted_y))) + + + # embed remember to separately embed the mask tokens + embedded_y = self.embed_y(cated_y, mask_position, mask_value) #BTD + assert embedded_y.shape[1:] == torch.Size((max(new_y_lens), self.args.d_model)), embedded_y.shape + + # positional embedding + y_input = self.audio_positional_embedding(embedded_y) + + # make attention mask and padding mask + y_padding_mask = make_pad_mask(new_y_lens).to(y.device) + y_attention_mask = torch.triu(torch.ones(y_input.shape[1], y_input.shape[1]), diagonal=1).bool().to(y_padding_mask.device) + return y_input, new_y_lens, targets, y_padding_mask, y_attention_mask, mask_position, patterns + + def remove_mask(self, logits, mask_position, new_y_lens): + # logits: [B K S card] + logits_use = [] + for i in range(len(logits)): + non_mask_positions = [-1] + mask_position[i] + [new_y_lens[i]] + non_mask_intervals = [[non_mask_positions[i]+1, non_mask_positions[i+1]] for i in range(len(non_mask_positions)-1)] + cur_logits_use = [logits[i, :, l:r] for l,r in non_mask_intervals] + logits_use.append(cur_logits_use) + + return logits_use + + def revert_pattern(self, patterns, logits_use): + logits_final = [] + logit_masks = [] + for i in range(len(logits_use)): + cur_logits = [ + item.unsqueeze(0).permute(0, 3, 1, 2).contiguous() for item in logits_use[i] + ] # each item is of shape [1 K S card] [1 card K S] + cur_logits_final = [ + cur_pattern.revert_pattern_logits( + item, 0, keep_only_valid_steps=False + ) + for cur_pattern, item in zip(patterns[i], cur_logits) + ] # if input output order doesn't match, this step will give an error + cur_logits_final_ret = [item[0].permute(0,2,3,1).squeeze(0) for item in cur_logits_final] # each element is of shape [K,T,card] + logits_final.append(cur_logits_final_ret) + logit_masks.append([item[2] for item in cur_logits_final]) + + return logits_final, logit_masks + + def dec_forward( + self, + x_input, + x_lens, + x_attention_mask, + x_padding_mask, + y_input, + new_y_lens, + y_attention_mask, + y_padding_mask, + past=None, + last_3_tokens=False + ): + x_attn_mask = F.pad( + x_attention_mask, + (0, new_y_lens.max()), + value=True, + ) # x attn to all x, doesn't attn to any y, this follow figure 3 of the valle paper + y_attn_mask = F.pad( + y_attention_mask, + (x_lens.max(), 0), # y is padded at the front + value=False, + ) # y attn to all x, for y itself use lower triangle mask to ensure autoregressive + xy_attn_mask = torch.concat([x_attn_mask, y_attn_mask], dim=0) + + # merge key padding and attention masks + bsz, src_len = x_input.shape[0], x_lens.max() + new_y_lens.max() + xy_padding_mask = torch.concat([x_padding_mask, y_padding_mask], dim=1) + _xy_padding_mask = ( + xy_padding_mask.view(bsz, 1, 1, src_len) + .expand(-1, self.args.nhead, -1, -1) + .reshape(bsz * self.args.nhead, 1, src_len) + ) + xy_attn_mask = xy_attn_mask.logical_or(_xy_padding_mask) + + new_attn_mask = torch.zeros_like(xy_attn_mask) + new_attn_mask.masked_fill_(xy_attn_mask, float("-inf")) + xy_attn_mask = new_attn_mask + + xy_input = torch.cat([x_input, y_input], dim=1) + + if past == None: # do not use kvcache + out, _ = self.decoder((xy_input, None), mask=xy_attn_mask) + return out[:, x_lens.max():], None + else: # use kvcache + if past.ndim > 3: # uses kvcache, only need to pass the last tokens, this doesn't work with multi-span speech editing yet + if last_3_tokens: + xy_input = xy_input[:, -3:] + xy_attn_mask = xy_attn_mask[:, -3:] + else: + xy_input = xy_input[:, -1:] + xy_attn_mask = xy_attn_mask[:, -1:] + + out, present = self.decoder((xy_input, None), mask=xy_attn_mask, past=past) + if isinstance(out, tuple): # get rid of stage_embedding + out = out[0] + + if out.shape[1] > x_lens.max(): # the first pass, not kvcache yet + return out[:, x_lens.max():], present + else: # used kvcache + return out, present + + def forward(self, batch): + """ + Args: + x: + A 2-D tensor of shape (N, S). + x_lens: + A 1-D tensor of shape (N,). It contains the number of tokens in `x` + before padding. + y: + A 3-D tensor of shape (N, K, T). + where K is the number of codebooks + y_lens: + A 1-D tensor of shape (N,). It contains the number of tokens in `x` + before padding. + """ + x, x_lens, y, y_lens = batch["x"], batch["x_lens"], batch["y"], batch["y_lens"] + x = x[:, :x_lens.max()] # this deal with gradient accumulation, where x_lens.max() might not be longer than the length of the current slice of x + y = y[:, :y_lens.max()] + assert x.ndim == 2, x.shape + assert x_lens.ndim == 1, x_lens.shape + assert y.ndim == 3 and y.shape[1] == self.args.n_codebooks, y.shape + assert y_lens.ndim == 1, y_lens.shape + # makes attention mask and padding mask for x + x_padding_mask = make_pad_mask(x_lens).to(x.device) + x_attention_mask = torch.triu(torch.ones(x.shape[1], x.shape[1]), diagonal=1).bool().to(x_padding_mask.device) + x_input = self.text_embedding(x) + x_input = self.text_positional_embedding(x_input) + y_input, new_y_lens, targets, y_padding_mask, y_attention_mask, mask_position, patterns = self.prepare_input_target(y, y_lens) + y_out = self.dec_forward( + x_input, + x_lens, + x_attention_mask, + x_padding_mask, + y_input, + new_y_lens, + y_attention_mask, + y_padding_mask + ) + y_out = y_out[0] # no kv-caching during training + assert y_out.shape == y_input.shape, f"y_out.shape: {y_out.shape}, y_input.shape: {y_input.shape}" # [B S D] + + logits = torch.stack([self.predict_layer[i](y_out) for i in range(self.args.n_codebooks)], dim=1) # [B K S card] + # take out the mask token (using mask_position and new_y_lens) and revert (using function provided by self.pattern) + assert logits.shape[1] == self.args.n_codebooks and logits.shape[3] == self.n_audio_tokens[0], logits.shape + + logits_use = self.remove_mask(logits, mask_position, new_y_lens) + + # revert the pattern shift for each logits section in each sample + logits_final, logit_masks = self.revert_pattern(patterns, logits_use) + assert logits_final[0][0].shape[0] == self.args.n_codebooks and logits_final[0][0].shape[2] == self.n_audio_tokens[0], f"it is: {logits_final[0][0].shape}, but should be [K, T, card]" + # testing + sample_to_test = 0 + assert len(logits_final[sample_to_test]) == len(targets[sample_to_test]), f"{len(logits_final[sample_to_test])}, {len(targets[sample_to_test])}" + temp = sum([logits_final[sample_to_test][i].shape[:-1] != targets[sample_to_test][i].shape for i in range(len(targets[sample_to_test]))]) + assert temp == 0, f"none equal positions: {temp}, total number of elements: {len(targets[sample_to_test])}" + + logit_masked = sum([(item==False).any() for cur_mask in logit_masks for item in cur_mask]) + assert logit_masked == 0, logit_masks + + logits = torch.cat([torch.cat(item, dim=1) for item in logits_final], dim=1) # [K, T1+T2+T3+..., card] + targets = torch.cat([torch.cat(item, dim=1) for item in targets], dim=1) # [K, T1+T2+T3+...] + assert targets.shape[0] == logits.shape[0], f"{targets.shape}, {logits.shape}" + loss = [] + ntokens = [] + top10acc = [] + for k, (logit, target) in enumerate(zip(logits, targets)): + loss.append(F.cross_entropy(logit, target, reduction='mean', weight=self.class_weight.data if self.args.eog_weight!=1 else None)) + top10acc.append(self.accuracy_metrics[k](logit.detach(), target)) + ntokens.append(len(logit)) + + all_ntokens = sum(ntokens) + if self.args.codebook_weight != None: + codebook_weight = eval(self.args.codebook_weight) + else: + codebook_weight = [1.] * self.args.n_codebooks + loss = sum([l*nt*cw for l, nt, cw in zip(loss, ntokens, codebook_weight)]) + top10acc_by_codebook = [t10a*nt for t10a, nt in zip(top10acc, ntokens)] + top10acc = sum(top10acc_by_codebook) + ntokens = torch.tensor(all_ntokens).to(logits.device) + + return { + "loss": loss, + "top10acc": top10acc, + "top10acc_by_codebook": top10acc_by_codebook, + "effective_ntoken": ntokens, + } + + def inference( + self, + x: torch.Tensor, + x_lens: torch.Tensor, + y: torch.Tensor, + mask_interval: list[torch.Tensor], + top_k: int=-100, + top_p: float=1.0, + temperature: float=1.0, + stop_repetition: int=-1, + kvcache: int=1, + silence_tokens: list[int]=[1388,1898,131], + ) -> torch.Tensor: + """ + Args: + x: + A 2-D tensor of shape (1, L). + x_lens: + A 1-D tensor of shape (1,). It contains the number of tokens in `x` + before padding. + y: + A 3-D tensor of shape (1, T, K). + mask_interval: + a list of tensors of shape (M, 2). contains M mask_start and mask_end. list length is actually 1, because we only support single sample inference for now + top_k: (`optional`) int + The number of highest probability tokens to keep for top-k-filtering. Default to -100. + top_p: (`optional`) float + For Neucleus sampling + temperature: (`optional`) float + The value used to module the next token probabilities. Must be strictly positive. Default to 1.0. + eog_coef: (`optional`) float + if 0, no change to eog token logits, otherwise, will adjust eog token logit based on the difference between acoustic token and phn token length + stop_repetition (`optional`) int + if not -1, will set the logits of a token that repeated this many times to be -100000, to avoid generating it again. This only apply to tokens from the first codebook + allowed_repeat_tokens (`optional`) list of ints + by inspecting the validation set, get a few tokens that indeed repeat a significant amount of time, and exclude those tokens from prevent repetition + ultimate_stop_repetition (`optional`) int + no matter that token it is, stop repetition once after this number + """ + assert x.ndim == 2, x.shape + assert x_lens.ndim == 1, x_lens.shape + assert y.ndim == 3, y.shape + if self.args.special_first: + y = y + int(self.args.n_special) + y = y.transpose(2,1) # [1,T,K] -> [1,K,T] + assert y.shape[0] == 1 and y.shape[1] == self.args.n_codebooks, y.shape # there is no padding + assert mask_interval.shape == torch.Size((1, mask_interval.shape[1], 2)), mask_interval + + # make x attention mask and x_input + x_attention_mask = torch.triu(torch.ones(x.shape[1], x.shape[1]), diagonal=1).bool().to(x.device) + # x_attention_mask = torch.zeros(x.shape[1], x.shape[1]).bool().to(x.device) + x_input = self.text_embedding(x) + x_input = self.text_positional_embedding(x_input) + + # make initial y_input + + # make mask_interval and non_mask_interval + y_len = y.shape[2] + y_lens = torch.LongTensor([y_len]).to(y.device) + mask_interval = mask_interval[0] + starts = [item[0].item() for item in mask_interval] + [y_len] + ends = [0] + [item[1].item() for item in mask_interval] + mask_intervals = [[ + (item[0].item(), item[1].item()) for item in mask_interval + ]] # a werid name change, mask_interval is input, now is mask_intervals, with one more dimension + non_mask_intervals = [[ + (ns, ne) for ns, ne in zip(ends, starts) + ]] + + # rearrange y + # will add have EOG in each section (SOG will be generated by the pattern class) + # but mask can be inserted later after we have shifted the input + # y could be rearranged in this way: + # [ + # [tensor[4, 12], tensor[4, 45], tensor[4, 102], tensor[4, 32]], tensor[4, 22]], + # [tensor[4, 44], tensor[4, 56], tensor[4, 19]], + # ... + # ] + # for the first list of tensors (4 tensors), first 3 tensors are non_masked part, last 2 are masked part. + # NOTE #non_masked_part = #masked_part + 1 + rearranged_y = self.rearrange(y, non_mask_intervals, mask_intervals) + assert rearranged_y[0][0].shape[0] == self.args.n_codebooks, rearranged_y[0][0].shape + + # shift each element of y + # next we need to apply pattern shifting to each tensor, after which, we'll replace the starting tokens of each section with a token that's different from the special padding token + # [ + # [empty, 1, 2, 3, eog, empty, empty, empty], + # [empty, empty, 1, 2, 3, eog, empty, empty], + # [empty, empty, empty, 1, 2, 3, eog, empty], + # [empty, empty, empty, empty, 1, 2, 3, eog] + # ] + shifted_y, patterns = self.shift(rearranged_y) # each element [K S], patterns is not used, as we directly use the original input y + assert shifted_y[0][0].shape[0] == self.args.n_codebooks, shifted_y[0][0].shape + + # insert mask token at the intersction of each tensor, but *actually inserted eog as place holder* + # the position of inserted mask is also recorded + # and the mask_value, the index of the mask emb is recorded + inserted_y, mask_position, mask_value = self.insert_mask(shifted_y) + assert inserted_y[0][0].shape[0] == self.args.n_codebooks, inserted_y[0][0].shape[0] + assert inserted_y[0][1].shape == torch.Size((self.args.n_codebooks, 1)), f"this should be a mask, so should have shape {(self.args.n_codebooks, 1)}, but it's {inserted_y[0][1].shape}" + + # then concat tensors that belong to the same sample (in order) then get the length of each sample, and then stack them in batch dimension, pad them with pad_token + cated_y, new_y_lens = self.cat_y(inserted_y, mask_position, y_lens) # KTB + assert cated_y.shape == torch.Size((self.args.n_codebooks, cated_y.shape[1], len(inserted_y))) + assert not (cated_y == self.args.audio_pad_token).any(), cated_y + + ### NOTE this is different from forward, as we will remove the masked tokens + ### say there are two masked region + ### the cated_y should be like + ### [empty a a a a mask0 empty b b b mask1 empty c c mask0 empty] + ### which means we need to take the part after the last empty out + num_mask = len(mask_position[0])//2 + assert num_mask == len(mask_position[0])/2, mask_position + cated_y = cated_y[:, :mask_position[0][num_mask]+2] # of shape [K,T,B] + # logging.info(f"mask_position[0][num_mask]+2: {mask_position[0][num_mask]+2}") + more_mask_value = mask_value[0][num_mask+1:] # NOTE this will be used in the generation loop for reference for inserting mask embedding + new_y_lens[0] = mask_position[0][num_mask]+2 + mask_position[0] = mask_position[0][:num_mask+1] + assert mask_position[0][num_mask]+2 == cated_y.shape[1], f"num_mask: {num_mask}, mask_position: {mask_position}, cated_y.shape: {cated_y.shape}" + + # embed: remember to separately embed the mask tokens + embedded_y = self.embed_y(cated_y, mask_position, [mask_value[0][:num_mask+1]]) #BTD + # assert embedded_y.shape == torch.Size((y.shape[0], max(new_y_lens), self.args.d_model)), embedded_y.shape + + # positional embedding + y_input = self.audio_positional_embedding(embedded_y) + + # make attention mask and padding mask + y_attention_mask = torch.triu(torch.ones(y_input.shape[1], y_input.shape[1]), diagonal=1).bool().to(y.device) + # y_lens = torch.LongTensor([y_input.shape[1]]).to(y.device) + + x_padding_mask = torch.full((1,x_lens[0]), False).to(x.device) + y_padding_mask = torch.full((1,new_y_lens[0]), False).to(y.device) + + + codebook_eog = [False] * self.args.n_codebooks + generated = [] # doesn't contain any empty_token, contains eog + cur_generated = [] + # say 0 is empty, 4 is eog + # tensor([[ 1, 2, 3, 4, 0, 0], + # [ 0, 1, 2, 3, 4, 0], + # [ 0, 0, 1, 2, 3, 4]]) + num_gen = [] + cur_num_gen = 0 + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + logging.info(f"silence tokens: {silence_tokens}, note that if you are not using the pretrained encodec 6f79c6a8, make sure you specified it yourself, rather than using the default") + consec_silence_count = 0 + prev_token = None + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + # prepare the cache placeholder + # n_layers, 2, bsz, num_heads, src_len, head_dim + past = torch.ones([self.args.num_decoder_layers, 2, x.shape[0]], device=x.device, dtype=torch.float32) if kvcache else None + # handle multi-span kv-cache + new_masked_span = False + + def sample_helper(n_eog, logits, codebook_eog, top_k, top_p, temperature, prev_token, consec_silence_count, stop_repetition, silence_tokens, cur_num_gen): + if n_eog == 0: + logits_adjust = logits + for jj in range(1,self.args.n_codebooks): + logits_adjust[jj][self.args.eog] = -10000 + logits_adjust[jj][self.args.empty_token] = -10000 + ##################### silence repetition handling ##################### + if stop_repetition > 0 and prev_token in silence_tokens and consec_silence_count > stop_repetition: + if logits_adjust[0, prev_token] < 0: + logits_adjust[0, prev_token] = logits_adjust[0, prev_token] * (consec_silence_count - (stop_repetition-1)) + else: + logits_adjust[0, prev_token] = logits_adjust[0, prev_token] / (consec_silence_count - (stop_repetition-1)) + ##################### silence repetition handling ##################### + if type(logits_adjust) == list: + samples_list= [] + for logit in logits_adjust: + # print(logit) + # print(logit.shape) + cur_sample = topk_sampling( + logit.unsqueeze(0), top_k=top_k, top_p=top_p, temperature=temperature + ) # [1, 1] + samples_list.append(cur_sample) + samples = torch.cat(samples_list, dim=0) # [K, 1] + else: + samples = topk_sampling( + logits_adjust, top_k=top_k, top_p=top_p, temperature=temperature + ) # [K, 1] + assert samples.shape == torch.Size((self.args.n_codebooks, 1)), f"samples.shape: {samples.shape}" + if cur_num_gen < self.args.n_codebooks-1: + for jj in range(1, self.args.n_codebooks - cur_num_gen): + samples[-jj, 0] = self.args.empty_token + + if ( + samples[0,0] == self.args.eog or torch.argmax(logits[0], dim=-1) == self.args.eog or y_input.shape[1] > x_lens[0] * 10 + ): # last one means y is already too long, shouldn't happen, but put it here + samples[0,0] = self.args.eog + codebook_eog[0] = True + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + if samples[0,0] in silence_tokens and samples[0,0] == prev_token: + consec_silence_count += 1 + else: + consec_silence_count = 0 + prev_token = samples[0,0] + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + return samples, codebook_eog, prev_token, consec_silence_count + else: + assert sum(codebook_eog[i] for i in range(n_eog)) == n_eog, f"codebook_eog: {codebook_eog}, but n_eog: {n_eog}" + logits_adjust = logits + for jj in range(n_eog+1,self.args.n_codebooks): + logits_adjust[jj][self.args.eog] = -10000 + logits_adjust[jj][self.args.empty_token] = -10000 + if type(logits_adjust) == list: + samples_list= [] + for logit in logits_adjust: + cur_sample = topk_sampling( + logit.unsqueeze(0), top_k=top_k, top_p=top_p, temperature=temperature + ) # [1, 1] + samples_list.append(cur_sample) + samples = torch.cat(samples_list, dim=0) # [K, 1] + else: + samples = topk_sampling( + logits_adjust, top_k=top_k, top_p=top_p, temperature=temperature + ) # [K, 1] + for jj in range(n_eog): + samples[jj, 0] = self.args.empty_token + samples[n_eog, 0] = self.args.eog + codebook_eog[n_eog] = True + return samples, codebook_eog, prev_token, consec_silence_count + + while True: + y_out, present = self.dec_forward( + x_input, + x_lens, + x_attention_mask, + x_padding_mask, + y_input, + new_y_lens, + y_attention_mask, + y_padding_mask, + past=past, + last_3_tokens = new_masked_span + ) + if new_masked_span: + new_masked_span = False + + if past != None: + past = torch.cat([past, present.to(past.dtype)], dim=-2) if past.ndim > 3 else present.to(past.dtype) + + y_out = y_out[:, -1:] # only take the last one + + logits = torch.stack([self.predict_layer[i](y_out) for i in range(self.args.n_codebooks)], dim=1) # [B K S card], B==S==1, so [1 K 1 card] + logits = logits.squeeze(0).squeeze(1) # [K card] + assert logits.shape == torch.Size((self.args.n_codebooks, self.n_audio_tokens[0])), f"{logits.shape}" + + n_eog = sum(codebook_eog) + assert n_eog < self.args.n_codebooks + if self.args.eos > 0: # eos stands for end-of-sentence, which shouldn't be used as we are doing speech editing + for jj in range(self.args.n_codebooks): + logits[jj][self.args.eos] = -10000. + # need to use a helper function to hand different n_eog cases + samples, codebook_eog, prev_token, consec_silence_count = sample_helper(n_eog, logits, codebook_eog, top_k, top_p, temperature, prev_token, consec_silence_count, stop_repetition, silence_tokens, cur_num_gen) + cur_num_gen += 1 + cur_generated.append(samples.squeeze(-1)) # [K,1] -> [K] + # get samples_emb + samples_emb = torch.stack([self.audio_embedding[k](samples[k]) for k in range(self.args.n_codebooks)], dim=0) # [K,1,D] + samples_emb = samples_emb.sum(dim=0,keepdim=True) # [1,1,D] + + if sum(codebook_eog) == self.args.n_codebooks: # generation for the current span is done + # re-init + codebook_eog = [False] * self.args.n_codebooks + num_gen.append(cur_num_gen) + cur_num_gen = 0 + generated.append(cur_generated) + cur_generated = [] + + # if the current mask span is the last span, then all done + # else + # append the next mask token and the four empty tokens to start the next generation + if len(more_mask_value) > 0: + next_mask_ind = more_mask_value.pop(0) + mask_emb = self.mask_embedding[next_mask_ind].unsqueeze(0).unsqueeze(0) # [1,1,D] + assert mask_emb.shape == torch.Size((1,1,self.args.d_model)), mask_emb.shape + empty_token = torch.LongTensor([self.args.empty_token]).to(y.device) + empty_emb = torch.stack([ + self.audio_embedding[k](empty_token) for k in range(self.args.n_codebooks)], dim=0 + ).sum(dim=0, keepdim=True) # [1,1,D] + assert empty_emb.shape == torch.Size((1,1,self.args.d_model)), empty_emb.shape + extra_emb = torch.cat([mask_emb, empty_emb], dim=1) # [1,2,D] + samples_emb = torch.cat([samples_emb, extra_emb], dim=1) # [1,3,D] # prev_last_token, mask_token, empty token + assert samples_emb.shape == torch.Size((1,3,self.args.d_model)), f"samples_emb.shape: {samples_emb.shape}" + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + consec_silence_count = 0 + prev_token = None + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + + # handling kv-caching for multi-span editing + new_masked_span = True + else: + break + else: + assert samples_emb.shape == torch.Size((1,1,self.args.d_model)), f"samples_emb.shape: {samples_emb.shape}" + + embedded_y = torch.cat([embedded_y, samples_emb], dim=1) + # positional embedding + y_input = self.audio_positional_embedding(embedded_y) # [B T D] + # make attention mask and padding mask + y_attention_mask = torch.triu(torch.ones(y_input.shape[1], y_input.shape[1]), diagonal=1).bool().to(y.device) + new_y_lens = torch.LongTensor([y_input.shape[1]]).to(y.device) + y_padding_mask = torch.full((1,new_y_lens[0]), False).to(y.device) + + assert len(generated) == num_mask, f"len(generated): {len(generated)}, num_mask: {num_mask}" + + # # combine non_masked_span with generated spans + # first need to shift the generated part back + flatten_gen = [] + for l, orig_span in enumerate(generated): + span = torch.stack(orig_span, dim=0) # [T K] + span = span.transpose(1,0) # [K, T] + assert span.shape[0] == self.args.n_codebooks, span.shape + unshifted_span = [] + for j, s in enumerate(span): + start_from = j + end_at = - (self.args.n_codebooks - start_from) + unshifted_span.append(s[start_from:end_at]) + unshifted_span = torch.stack(unshifted_span, dim=0) + + assert unshifted_span.shape[1] == num_gen[l] - self.args.n_codebooks, f"len(unshifted_spans[0]): {len(unshifted_span[0])}, num_gen[l]: {num_gen[l]}" + flatten_gen.append(unshifted_span) + # logging.info(f"unshfited_span: {unshifted_span.shape}") + # raise + assert len(non_mask_intervals[0]) - 1 == len(flatten_gen), f"len(non_mask_intervals[0]): {len(non_mask_intervals[0])}, len(flatten_gen): {len(flatten_gen)}" + res = [] + for orig_interval, gen in zip(non_mask_intervals[0], flatten_gen): + res.append(y[0, :, orig_interval[0]:orig_interval[1]]) + res.append(gen) + res.append(y[0, :, non_mask_intervals[0][-1][0]:non_mask_intervals[0][-1][1]]) + res = torch.cat(res, dim=1).unsqueeze(0) # [K,new_T] -> [1, K, new_T] + + expected_y_len = y_len - sum([item[1] - item[0] for item in mask_intervals[0]]) + sum([item - self.args.n_codebooks for item in num_gen]) + assert res.shape == torch.Size((1, self.args.n_codebooks, expected_y_len)), f"res.shape: {res.shape}, expected_y_len: {expected_y_len}. y_len - sum([item[1] - item[0] for item in mask_interval]) + sum([item - self.args.n_codebooks for item in num_gen]): {y_len}-{sum([item[1] - item[0] for item in mask_interval])} + {sum([item - self.args.n_codebooks for item in num_gen])}" + + if self.args.special_first: + res = res - int(self.args.n_special) + + return res + + def inference_tts( + self, + x: torch.Tensor, + x_lens: torch.Tensor, + y: torch.Tensor, + top_k: int=-100, + top_p: float=1.0, + temperature: float=1.0, + stop_repetition: int=3, + kvcache: int=1, + silence_tokens: list[int]=[1388,1898,131], + *kargs + ) -> torch.Tensor: + """ + different from inference_tts, this implementation uses kvcache, which should have significant speed up + Args: + x: + A 2-D tensor of shape (1, L). + x_lens: + A 1-D tensor of shape (1,). It contains the number of tokens in `x` + before padding. + y: + A 3-D tensor of shape (1, T, K). + top_k: (`optional`) int + The number of highest probability tokens to keep for top-k-filtering. Default to -100. + top_p: (`optional`) float + For Neucleus sampling + temperature: (`optional`) float + The value used to module the next token probabilities. Must be strictly positive. Default to 1.0. + """ + eog_inference = self.args.eos if self.args.eos>0 else self.args.eog + assert x.ndim == 2, x.shape + assert x_lens.ndim == 1, x_lens.shape + assert y.ndim == 3, y.shape + if self.args.special_first: + y = y + int(self.args.n_special) + y = y.transpose(2,1) # [1,T,K] -> [1,K,T] + assert y.shape[0] == 1 and y.shape[1] == self.args.n_codebooks, y.shape # there is no padding + + # make x attention mask and x_input + x_attention_mask = torch.triu(torch.ones(x.shape[1], x.shape[1]), diagonal=1).bool().to(x.device) + # x_attention_mask = torch.zeros(x.shape[1], x.shape[1]).bool().to(x.device) + x_input = self.text_embedding(x) + x_input = self.text_positional_embedding(x_input) + + y_len = y.shape[2] + y_lens = torch.LongTensor([y_len]).to(y.device) + + # rearrange y, we don't add eog to the end, this doesn't actually do anything in the tts scenario + rearranged_y = [[y[0]]] + assert rearranged_y[0][0].shape[0] == self.args.n_codebooks, rearranged_y[0][0].shape + + # shift y to create the delayed pattern + shifted_y, patterns = self.shift(rearranged_y) # each element [K S], patterns is not used, as we directly use the original input y + assert shifted_y[0][0].shape[0] == self.args.n_codebooks, shifted_y[0][0].shape + assert len(shifted_y[0]) == 1, len(shifted_y[0]) + + # below is different from forward or inference + # where we cut this shifted part + shifted_y[0][0] = shifted_y[0][0][:, :-(self.args.n_codebooks-1)] + assert not (shifted_y[0][0][self.args.n_codebooks:] == self.args.empty_token).any() and not (shifted_y[0][0][self.args.n_codebooks:] == self.args.eog).any(), shifted_y[0][0] + + # next section in inference is insert mask at the intersection of each tensor in a sample, but we don't need to do that + # next section is concate tensors of each sample to one tensor, which we also don't need + cated_y = shifted_y[0][0].unsqueeze(-1) #[K,S]->[K,S,B] + new_y_lens = torch.LongTensor([cated_y.shape[1]]).to(cated_y.device) + assert cated_y.shape == torch.Size((self.args.n_codebooks, cated_y.shape[1], 1)) + assert not (cated_y == self.args.audio_pad_token).any(), cated_y + + # replace tokens in y with the embeddings, add sum codebooks up + embedded_y = torch.stack([self.audio_embedding[k](cated_y[k]) for k in range(self.args.n_codebooks)], dim=0) # [K, S, B, D] + assert embedded_y.shape[0] == self.args.n_codebooks, embedded_y.shape + assert embedded_y.shape[-1] == self.args.d_model, embedded_y.shape + embedded_y = embedded_y.sum(dim=0) # [K,S,B,D]->[S,B,D] + embedded_y = embedded_y.transpose(1,0) # [S,B,D]->[B,S,D] + + # positional embedding + y_input = self.audio_positional_embedding(embedded_y) + + # make attention mask and padding mask + y_attention_mask = torch.triu(torch.ones(y_input.shape[1], y_input.shape[1]), diagonal=1).bool().to(y.device) + + x_padding_mask = torch.full((1,x_lens[0]), False).to(x.device) + y_padding_mask = torch.full((1,new_y_lens[0]), False).to(y.device) + + # entering the generation stage + # starting from line 708 + codebook_eog = [False] * self.args.n_codebooks + generated = [] # doesn't contain any empty token, contain eog + cur_generated = [] + # say 0 is empty, 4 is eog + # tensor([[ 1, 2, 3, 4, 0, 0], + # [ 0, 1, 2, 3, 4, 0], + # [ 0, 0, 1, 2, 3, 4]]) + num_gen = [] + cur_num_gen = 0 + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + logging.info(f"silence tokens: {silence_tokens}, note that if you are not using the pretrained encodec 6f79c6a8, make sure you specified it yourself, rather than using the default") + consec_silence_count = 0 + prev_token = None + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + + # prepare the cache placeholder + # n_layers, 2, bsz, num_heads, src_len, head_dim + past = torch.ones([self.args.num_decoder_layers, 2, x.shape[0]], device=x.device, dtype=torch.float32) if kvcache else None + # logging.info(f"number of decoder layers: {self.args.num_decoder_layers}") + # logging.info(f"number of decoder layers: {self.args.num_decoder_layers}") + # logging.info(f"number of decoder layers: {self.args.num_decoder_layers}") + def sample_helper(n_eog, logits, codebook_eog, top_k, top_p, temperature, prev_token, consec_silence_count, stop_repetition, silence_tokens, cur_num_gen): + if n_eog == 0: + logits_adjust = logits + for jj in range(1,self.args.n_codebooks): + logits_adjust[jj][eog_inference] = -10000 + logits_adjust[jj][self.args.empty_token] = -10000 + ##################### silence repetition handling ##################### + if stop_repetition > 0 and prev_token in silence_tokens and consec_silence_count > stop_repetition: + if logits_adjust[0, prev_token] < 0: + logits_adjust[0, prev_token] = logits_adjust[0, prev_token] * (consec_silence_count - (stop_repetition-1)) + else: + logits_adjust[0, prev_token] = logits_adjust[0, prev_token] / (consec_silence_count - (stop_repetition-1)) + ##################### silence repetition handling ##################### + samples = topk_sampling( + logits_adjust, top_k=top_k, top_p=top_p, temperature=temperature + ) # [K, 1] + assert samples.shape == torch.Size((self.args.n_codebooks, 1)), f"samples.shape: {samples.shape}" + if cur_num_gen < self.args.n_codebooks-1: + for jj in range(1, self.args.n_codebooks - cur_num_gen): + samples[-jj, 0] = self.args.empty_token + + if ( + samples[0,0] == eog_inference or torch.argmax(logits[0], dim=-1) == eog_inference or y_input.shape[1] > x_lens[0] * (self.args.encodec_sr//5) + ): # last one means y is already too long, shouldn't happen, but put it here + samples[0,0] = eog_inference + codebook_eog[0] = True + ##################### silence repetition handling ##################### + if samples[0,0] in silence_tokens and samples[0,0] == prev_token: + consec_silence_count += 1 + else: + consec_silence_count = 0 + prev_token = samples[0,0] + ##################### silence repetition handling ##################### + return samples, codebook_eog, prev_token, consec_silence_count + else: + assert sum(codebook_eog[i] for i in range(n_eog)) == n_eog, f"codebook_eog: {codebook_eog}, but n_eog: {n_eog}" + logits_adjust = logits + for jj in range(n_eog+1,self.args.n_codebooks): + logits_adjust[jj][eog_inference] = -10000 + logits_adjust[jj][self.args.empty_token] = -10000 + samples = topk_sampling( + logits_adjust, top_k=top_k, top_p=top_p, temperature=temperature + ) # [K, 1] + for jj in range(n_eog): + samples[jj, 0] = self.args.empty_token + samples[n_eog, 0] = eog_inference + codebook_eog[n_eog] = True + return samples, codebook_eog, prev_token, consec_silence_count + while True: + y_out, present = self.dec_forward( + x_input, + x_lens, + x_attention_mask, + x_padding_mask, + y_input, + new_y_lens, + y_attention_mask, + y_padding_mask, + past=past + ) + if past != None: + past = torch.cat([past, present.to(past.dtype)], dim=-2) if past.ndim > 3 else present.to(past.dtype) + + + y_out = y_out[:, -1:] # only take the last token + logits = torch.stack([self.predict_layer[i](y_out) for i in range(self.args.n_codebooks)], dim=1) # [B K S card], B==S==1, so [1 K 1 card] + logits = logits.squeeze(0).squeeze(1) # [K card] + assert logits.shape == torch.Size((self.args.n_codebooks, self.n_audio_tokens[0])), f"{logits.shape}" + + n_eog = sum(codebook_eog) + assert n_eog < self.args.n_codebooks + if self.args.eos > 0: # if we are using end-of-sentence token (which is used by default), eog shouldn't be used here, as there is no masked spans + for jj in range(self.args.n_codebooks): + logits[jj][self.args.eog] = -10000. + + samples, codebook_eog, prev_token, consec_silence_count = sample_helper(n_eog, logits, codebook_eog, top_k, top_p, temperature, prev_token, consec_silence_count, stop_repetition, silence_tokens, cur_num_gen) + + cur_num_gen += 1 + cur_generated.append(samples.squeeze(-1)) # [K,1] -> [K] + + # samples.shape is [K,1] + # ge samples_emb + samples_emb = torch.stack([self.audio_embedding[k](samples[k]) for k in range(self.args.n_codebooks)], dim=0) # [K,1,D] + samples_emb = samples_emb.sum(dim=0,keepdim=True) # [1,1,D] + + if sum(codebook_eog) == self.args.n_codebooks: # generation for the current span is done + codebook_eog = [False] * self.args.n_codebooks + num_gen.append(cur_num_gen) + cur_num_gen = 0 + generated.append(cur_generated) + cur_generated = [] + break + else: + assert samples_emb.shape == torch.Size((1,1,self.args.d_model)), f"samples_emb.shape: {samples_emb.shape}" + + embedded_y = torch.cat([embedded_y, samples_emb], dim=1) + y_input = self.audio_positional_embedding(embedded_y) # [B T D] + # make attention mask and padding mask + y_attention_mask = torch.triu(torch.ones(y_input.shape[1], y_input.shape[1]), diagonal=1).bool().to(y.device) + new_y_lens = torch.LongTensor([y_input.shape[1]]).to(y.device) + y_padding_mask = torch.full((1,new_y_lens[0]), False).to(y.device) + + assert len(generated) == 1, f"len(generated): {len(generated)}" + + # revert the pattern + flatten_gen = [] + for l, orig_span in enumerate(generated): + span = torch.stack(orig_span, dim=0) # [T, K] + span = span.transpose(1,0) # [K, T] + assert span.shape[0] == self.args.n_codebooks, span.shape + unshifted_span = [] + for j, s in enumerate(span): + start_from = j + end_at = - (self.args.n_codebooks - start_from) + unshifted_span.append(s[start_from:end_at]) + unshifted_span = torch.stack(unshifted_span, dim=0) + + assert unshifted_span.shape[1] == num_gen[l] - self.args.n_codebooks, f"len(unshifted_spans[0]): {len(unshifted_span[0])}, num_gen[l]: {num_gen[l]}" + + flatten_gen.append(unshifted_span) + assert len(flatten_gen) == 1, len(flatten_gen) + + # combine + res = [y[0], flatten_gen[0]] + res = torch.cat(res, dim=1).unsqueeze(0) # [K, new_t] -> [1, K, new_T] + + expected_y_len = y_len + sum([item - self.args.n_codebooks for item in num_gen]) + assert res.shape == torch.Size((1, self.args.n_codebooks, expected_y_len)), f"res.shape: {res.shape}, expected_y_len: {expected_y_len}. y_len + sum([item - self.args.n_codebooks for item in num_gen]): {y_len} + {sum([item - self.args.n_codebooks for item in num_gen])}" + + if self.args.special_first: + res = res - int(self.args.n_special) + flatten_gen = flatten_gen - int(self.args.n_special) + + return res, flatten_gen[0].unsqueeze(0) + + + def inference_tts_batch( + self, + x: torch.Tensor, + x_lens: torch.Tensor, + y: torch.Tensor, + top_k: int=-100, + top_p: float=1.0, + temperature: float=1.0, + stop_repetition: int=3, + kvcache: int=1, + batch_size: int=5, + silence_tokens: list[int]=[1388,1898,131], + *kargs + ) -> torch.Tensor: + """ + have a batch size when forward passing, but they are equivalant to same example but different random seed, therefore as long as one example generated eog, we can drop all other samlpes + different from inference_tts, this implementation uses kvcache, which should have significant speed up + Args: + x: + A 2-D tensor of shape (1, L). + x_lens: + A 1-D tensor of shape (1,). It contains the number of tokens in `x` + before padding. + y: + A 3-D tensor of shape (1, T, K). + top_k: (`optional`) int + The number of highest probability tokens to keep for top-k-filtering. Default to -100. + top_p: (`optional`) float + For Neucleus sampling + temperature: (`optional`) float + The value used to module the next token probabilities. Must be strictly positive. Default to 1.0. + """ + eog_inference = self.args.eos if self.args.eos>0 else self.args.eog + assert x.ndim == 2, x.shape + assert x_lens.ndim == 1, x_lens.shape + assert y.ndim == 3, y.shape + if self.args.special_first: + y = y + int(self.args.n_special) + y = y.transpose(2,1) # [1,T,K] -> [1,K,T] + assert y.shape[0] == 1 and y.shape[1] == self.args.n_codebooks, y.shape # there is no padding + + # make x attention mask and x_input + x_attention_mask = torch.triu(torch.ones(x.shape[1], x.shape[1]), diagonal=1).bool().to(x.device) + # x_attention_mask = torch.zeros(x.shape[1], x.shape[1]).bool().to(x.device) + x_input = self.text_embedding(x) + x_input = self.text_positional_embedding(x_input) + + y_len = y.shape[2] + y_lens = torch.LongTensor([y_len]).to(y.device) + + # rearrange y, we don't add eog to the end, this doesn't actually do anything in the tts scenario + rearranged_y = [[y[0]]] + assert rearranged_y[0][0].shape[0] == self.args.n_codebooks, rearranged_y[0][0].shape + + # shift y to create the delayed pattern + shifted_y, patterns = self.shift(rearranged_y) # each element [K S], patterns is not used, as we directly use the original input y + assert shifted_y[0][0].shape[0] == self.args.n_codebooks, shifted_y[0][0].shape + assert len(shifted_y[0]) == 1, len(shifted_y[0]) + + # below is different from forward or inference + # where we cut this shifted part + shifted_y[0][0] = shifted_y[0][0][:, :-(self.args.n_codebooks-1)] + assert not (shifted_y[0][0][self.args.n_codebooks:] == self.args.empty_token).any() and not (shifted_y[0][0][self.args.n_codebooks:] == self.args.eog).any(), shifted_y[0][0] + + # next section in inference is insert mask at the intersection of each tensor in a sample, but we don't need to do that + # next section is concate tensors of each sample to one tensor, which we also don't need + cated_y = shifted_y[0][0].unsqueeze(-1) #[K,S]->[K,S,B] + new_y_lens = torch.LongTensor([cated_y.shape[1]]).to(cated_y.device) + assert cated_y.shape == torch.Size((self.args.n_codebooks, cated_y.shape[1], 1)) + assert not (cated_y == self.args.audio_pad_token).any(), cated_y + + # replace tokens in y with the embeddings, add sum codebooks up + embedded_y = torch.stack([self.audio_embedding[k](cated_y[k]) for k in range(self.args.n_codebooks)], dim=0) # [K, S, B, D] + assert embedded_y.shape[0] == self.args.n_codebooks, embedded_y.shape + assert embedded_y.shape[-1] == self.args.d_model, embedded_y.shape + embedded_y = embedded_y.sum(dim=0) # [K,S,B,D]->[S,B,D] + embedded_y = embedded_y.transpose(1,0) # [S,B,D]->[B,S,D] + + # positional embedding + y_input = self.audio_positional_embedding(embedded_y) + + # make attention mask and padding mask + y_attention_mask = torch.triu(torch.ones(y_input.shape[1], y_input.shape[1]), diagonal=1).bool().to(y.device) + + x_padding_mask = torch.full((1,x_lens[0]), False).to(x.device) + y_padding_mask = torch.full((1,new_y_lens[0]), False).to(y.device) + + # entering the generation stage + # starting from line 708 + codebook_eog = [False] * self.args.n_codebooks + generated = [] # doesn't contain any empty token, contain eog + cur_generated = [[] for _ in range(batch_size)] + # say 0 is empty, 4 is eog + # tensor([[ 1, 2, 3, 4, 0, 0], + # [ 0, 1, 2, 3, 4, 0], + # [ 0, 0, 1, 2, 3, 4]]) + num_gen = [] + cur_num_gen = 0 + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + logging.info(f"silence tokens: {silence_tokens}, note that if you are not using the pretrained encodec 6f79c6a8, make sure you specified it yourself, rather than using the default") + consec_silence_counts = [0 for _ in range(batch_size)] + prev_tokens = [None for _ in range(batch_size)] + ##################### silence repetition handling ##################### + ##################### silence repetition handling ##################### + + # prepare the cache placeholder + # n_layers, 2, bsz, num_heads, src_len, head_dim + past = torch.ones([self.args.num_decoder_layers, 2, x.shape[0]], device=x.device, dtype=torch.float32) if kvcache else None + # logging.info(f"number of decoder layers: {self.args.num_decoder_layers}") + # logging.info(f"number of decoder layers: {self.args.num_decoder_layers}") + # logging.info(f"number of decoder layers: {self.args.num_decoder_layers}") + keep = None # NOTE: this very important, tells which sample to keep + def sample_helper(n_eog, logits, codebook_eog, top_k, top_p, temperature, prev_tokens, consec_silence_counts, stop_repetition, silence_tokens, cur_num_gen, keep): + if n_eog == 0: + logits_adjust = logits + for jj in range(1,self.args.n_codebooks): + logits_adjust[:,jj,eog_inference] = -10000 + logits_adjust[:,jj,self.args.empty_token] = -10000 + ##################### silence repetition handling ##################### + for b in range(batch_size): + prev_token = prev_tokens[b] + consec_silence_count = consec_silence_counts[b] + if stop_repetition > 0 and prev_token in silence_tokens and consec_silence_count > stop_repetition: + if logits_adjust[b, 0, prev_token] < 0: + logits_adjust[b, 0, prev_token] = logits_adjust[b, 0, prev_token] * (consec_silence_count - (stop_repetition-1)) + else: + logits_adjust[b, 0, prev_token] = logits_adjust[b, 0, prev_token] / (consec_silence_count - (stop_repetition-1)) + ##################### silence repetition handling ##################### + samples = topk_sampling( + logits_adjust.reshape(batch_size * self.args.n_codebooks, logits_adjust.shape[-1]), top_k=top_k, top_p=top_p, temperature=temperature + ) # [B*K, 1] + samples = samples.reshape(batch_size, self.args.n_codebooks, 1) + assert samples.shape == torch.Size((batch_size, self.args.n_codebooks, 1)), f"samples.shape: {samples.shape}" + for b in range(batch_size): + if cur_num_gen < self.args.n_codebooks-1: + for jj in range(1, self.args.n_codebooks - cur_num_gen): + samples[b, -jj, 0] = self.args.empty_token + + if ( + samples[b,0,0] == eog_inference or torch.argmax(logits[b,0], dim=-1) == eog_inference or y_input.shape[1] > x_lens[b] * (self.args.encodec_sr//5) + ): # last one means y is already too long, shouldn't happen, but put it here + samples[b,0,0] = eog_inference + codebook_eog[0] = True + keep = b # NOTE keep is a very important variable, we only return this one, note that if eog shows up in two samples, keep will be overwritten by the later one (or the last one) + ##################### silence repetition handling ##################### + if samples[b,0,0] in silence_tokens and samples[b,0,0] == prev_tokens[b]: + consec_silence_counts[b] += 1 + else: + consec_silence_counts[b] = 0 + prev_tokens[b] = samples[b,0,0] + ##################### silence repetition handling ##################### + return samples, codebook_eog, prev_tokens, consec_silence_counts, keep + else: + assert sum(codebook_eog[i] for i in range(n_eog)) == n_eog, f"codebook_eog: {codebook_eog}, but n_eog: {n_eog}" + logits_adjust = logits + for jj in range(n_eog+1,self.args.n_codebooks): + logits_adjust[:,jj,eog_inference] = -10000 + logits_adjust[:,jj,self.args.empty_token] = -10000 + samples = topk_sampling( + logits_adjust.reshape(batch_size * self.args.n_codebooks, logits_adjust.shape[-1]), top_k=top_k, top_p=top_p, temperature=temperature + ) # [B, K, 1] + samples = samples.reshape(batch_size, self.args.n_codebooks, 1) + for jj in range(n_eog): + samples[keep, jj, 0] = self.args.empty_token + samples[keep, n_eog, 0] = eog_inference + codebook_eog[n_eog] = True + return samples, codebook_eog, prev_tokens, consec_silence_counts, keep + while True: + # if cur_num_gen > 0, should have everything in kvcache, so only pass in the last token + # in the first generation step, we repeat each tensor to make their first dimension of length the batch size + if cur_num_gen == 0: + assert x_input.ndim == 3 and x_input.shape[0] == 1, x_input.shape + assert x_padding_mask.ndim == 2 and x_padding_mask.shape[0] == 1, x_padding_mask.shape + assert y_input.ndim == 3 and y_input.shape[0] == 1 and y_input.shape[1] == new_y_lens[0], y_input.shape + assert embedded_y.ndim == 3 and embedded_y.shape[0] == 1 and embedded_y.shape[1] == new_y_lens[0], embedded_y.shape + x_input = x_input.repeat(batch_size, 1, 1) + x_lens = x_lens.repeat(batch_size) + # x_attention_mask = x_attention_mask.repeat(batch_size, 1, 1) # no need to work with attention mask, it doesn't contain batch dimension + x_padding_mask = x_padding_mask.repeat(batch_size, 1) + y_input = y_input.repeat(batch_size, 1, 1) + new_y_lens = new_y_lens.repeat(batch_size) + # y_attention_mask = y_attention_mask.repeat(batch_size, 1, 1) # no need to work with attention mask, it doesn't contain batch dimension + y_padding_mask = y_padding_mask.repeat(batch_size, 1) + embedded_y = embedded_y.repeat(batch_size, 1, 1) # will be used to concat with newly generated token embedding + past = past.repeat(1, 1, batch_size) if past != None else None + else: + assert x_input.shape[0] == batch_size and x_padding_mask.shape[0] == batch_size and y_input.shape[0] == batch_size and new_y_lens.shape[0] == batch_size, f"x_input.shape: {x_input.shape}, x_padding_mask.shape: {x_padding_mask.shape}, y_input.shape: {y_input.shape}, new_y_lens.shape: {new_y_lens.shape}" + y_out, present = self.dec_forward( + x_input, + x_lens, + x_attention_mask, + x_padding_mask, + y_input, + new_y_lens, + y_attention_mask, + y_padding_mask, + past=past + ) + if past != None: + past = torch.cat([past, present.to(past.dtype)], dim=-2) if past.ndim > 3 else present.to(past.dtype) + + # if no eog emerges, y_out should have batch size of batch_size + if sum(codebook_eog) == 0: + assert y_out.shape[0] == batch_size and y_out.ndim == 3, y_out.shape + y_out = y_out[:, -1:] # only take the last token + logits = torch.stack([self.predict_layer[i](y_out) for i in range(self.args.n_codebooks)], dim=1) # [B K S card], S==1, so [B K 1 card] + logits = logits.squeeze(2) # [B K card] + assert logits.shape == torch.Size((batch_size, self.args.n_codebooks, self.n_audio_tokens[0])), f"{logits.shape}" + + n_eog = sum(codebook_eog) + if self.args.eos > 0: + for jj in range(self.args.n_codebooks): + logits[:,jj,self.args.eog] = -10000. + samples, codebook_eog, prev_tokens, consec_silence_counts, keep = sample_helper(n_eog, logits, codebook_eog, top_k, top_p, temperature, prev_tokens, consec_silence_counts, stop_repetition, silence_tokens, cur_num_gen, keep) + + cur_num_gen += 1 + if sum(codebook_eog) == 0: # no eog yet, keep batch_size of samples + assert keep == None + for b in range(batch_size): + cur_generated[b].append(samples[b].squeeze(-1)) + elif sum(codebook_eog) == 1: # the first eog just showed up in this step + assert keep != None + cur_generated = cur_generated[keep] + cur_generated.append(samples[keep].squeeze(-1)) + else: # we are generating the rest eogs for the 'keep' sample + cur_generated.append(samples[keep].squeeze(-1)) + + # samples.shape is [K,1] + # ge samples_emb + samples_emb = torch.stack([self.audio_embedding[k](samples[:, k]) for k in range(self.args.n_codebooks)], dim=1) # [B, K,1,D] + assert samples_emb.shape == torch.Size([batch_size, self.args.n_codebooks, 1, self.args.d_model]) + samples_emb = samples_emb.sum(dim=1,keepdim=False) # [B,1,D] + if sum(codebook_eog) == self.args.n_codebooks: # generation for the current span is done + codebook_eog = [False] * self.args.n_codebooks + num_gen.append(cur_num_gen) + cur_num_gen = 0 + generated.append(cur_generated) + cur_generated = [[] for _ in range(batch_size)] + break + else: + assert samples_emb.shape == torch.Size((batch_size,1,self.args.d_model)), f"samples_emb.shape: {samples_emb.shape}" + + embedded_y = torch.cat([embedded_y, samples_emb], dim=1) + y_input = self.audio_positional_embedding(embedded_y) # [B T D] + # make attention mask and padding mask + y_attention_mask = torch.triu(torch.ones(y_input.shape[1], y_input.shape[1]), diagonal=1).bool().to(y.device) + new_y_lens = torch.LongTensor([y_input.shape[1]]).to(y.device).repeat(batch_size) + y_padding_mask = torch.full((batch_size,new_y_lens[0]), False).to(y.device) + + assert len(generated) == 1, f"len(generated): {len(generated)}" + + # revert the pattern + flatten_gen = [] + for l, orig_span in enumerate(generated): + span = torch.stack(orig_span, dim=0) # [T, K] + span = span.transpose(1,0) # [K, T] + assert span.shape[0] == self.args.n_codebooks, span.shape + unshifted_span = [] + for j, s in enumerate(span): + start_from = j + end_at = - (self.args.n_codebooks - start_from) + unshifted_span.append(s[start_from:end_at]) + unshifted_span = torch.stack(unshifted_span, dim=0) + + assert unshifted_span.shape[1] == num_gen[l] - self.args.n_codebooks, f"len(unshifted_spans[0]): {len(unshifted_span[0])}, num_gen[l]: {num_gen[l]}" + + flatten_gen.append(unshifted_span) + assert len(flatten_gen) == 1, len(flatten_gen) + + # combine + res = [y[0], flatten_gen[0]] + res = torch.cat(res, dim=1).unsqueeze(0) # [K, new_t] -> [1, K, new_T] + + expected_y_len = y_len + sum([item - self.args.n_codebooks for item in num_gen]) + assert res.shape == torch.Size((1, self.args.n_codebooks, expected_y_len)), f"res.shape: {res.shape}, expected_y_len: {expected_y_len}. y_len + sum([item - self.args.n_codebooks for item in num_gen]): {y_len} + {sum([item - self.args.n_codebooks for item in num_gen])}" + + if self.args.special_first: + res = res - int(self.args.n_special) + flatten_gen = flatten_gen - int(self.args.n_special) + + return res, flatten_gen[0].unsqueeze(0) \ No newline at end of file diff --git a/steps/__init__.py b/steps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/steps/optim.py b/steps/optim.py new file mode 100644 index 0000000..88bd02b --- /dev/null +++ b/steps/optim.py @@ -0,0 +1,1123 @@ +# Copyright 2022 Xiaomi Corp. (authors: Daniel Povey) +# +# See ../LICENSE for clarification regarding multiple authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import contextlib +import logging +import random +from collections import defaultdict +from typing import List, Optional, Tuple, Union + +import torch +import torch.nn as nn +from torch import Tensor +from torch.optim import Optimizer + + +class BatchedOptimizer(Optimizer): + """ + This class adds to class Optimizer the capability to optimize parameters in batches: + it will stack the parameters and their grads for you so the optimizer can work + on tensors with an extra leading dimension. This is intended for speed with GPUs, + as it reduces the number of kernels launched in the optimizer. + + Args: + params: + """ + + def __init__(self, params, defaults): + super(BatchedOptimizer, self).__init__(params, defaults) + + @contextlib.contextmanager + def batched_params(self, param_group, group_params_names): + """ + This function returns (technically, yields) a list of + of tuples (p, state), where + p is a `fake` parameter that is stacked (over axis 0) from real parameters + that share the same shape, and its gradient is also stacked; + `state` is the state corresponding to this batch of parameters + (it will be physically located in the "state" for one of the real + parameters, the last one that has any particular shape and dtype). + + This function is decorated as a context manager so that it can + write parameters back to their "real" locations. + + The idea is, instead of doing: + + for p in group["params"]: + state = self.state[p] + ... + + you can do: + + with self.batched_params(group["params"]) as batches: + for p, state, p_names in batches: + ... + + + Args: + group: a parameter group, which is a list of parameters; should be + one of self.param_groups. + group_params_names: name for each parameter in group, + which is List[str]. + """ + batches = defaultdict( + list + ) # `batches` maps from tuple (dtype_as_str,*shape) to list of nn.Parameter + batches_names = defaultdict( + list + ) # `batches` maps from tuple (dtype_as_str,*shape) to list of str + + assert len(param_group) == len(group_params_names), f"len(param_group): {len(param_group)}, len(group_params_names): {len(group_params_names)}" + for p, named_p in zip(param_group, group_params_names): + key = (str(p.dtype), *p.shape) + batches[key].append(p) + batches_names[key].append(named_p) + + batches_names_keys = list(batches_names.keys()) + sorted_idx = sorted( + range(len(batches_names)), key=lambda i: batches_names_keys[i] + ) + batches_names = [ + batches_names[batches_names_keys[idx]] for idx in sorted_idx + ] + batches = [batches[batches_names_keys[idx]] for idx in sorted_idx] + + stacked_params_dict = dict() + + # turn batches into a list, in deterministic order. + # tuples will contain tuples of (stacked_param, state, stacked_params_names), + # one for each batch in `batches`. + tuples = [] + + for batch, batch_names in zip(batches, batches_names): + p = batch[0] + # we arbitrarily store the state in the + # state corresponding to the 1st parameter in the + # group. class Optimizer will take care of saving/loading state. + state = self.state[p] + p_stacked = torch.stack(batch) + grad = torch.stack( + [ + torch.zeros_like(p) if p.grad is None else p.grad + for p in batch + ] + ) + p_stacked.grad = grad + stacked_params_dict[key] = p_stacked + tuples.append((p_stacked, state, batch_names)) + + yield tuples # <-- calling code will do the actual optimization here! + + for ((stacked_params, _state, _names), batch) in zip(tuples, batches): + for i, p in enumerate(batch): # batch is list of Parameter + p.copy_(stacked_params[i]) + + +class ScaledAdam(BatchedOptimizer): + """ + Implements 'Scaled Adam', a variant of Adam where we scale each parameter's update + proportional to the norm of that parameter; and also learn the scale of the parameter, + in log space, subject to upper and lower limits (as if we had factored each parameter as + param = underlying_param * log_scale.exp()) + + + Args: + params: The parameters or param_groups to optimize (like other Optimizer subclasses) + lr: The learning rate. We will typically use a learning rate schedule that starts + at 0.03 and decreases over time, i.e. much higher than other common + optimizers. + clipping_scale: (e.g. 2.0) + A scale for gradient-clipping: if specified, the normalized gradients + over the whole model will be clipped to have 2-norm equal to + `clipping_scale` times the median 2-norm over the most recent period + of `clipping_update_period` minibatches. By "normalized gradients", + we mean after multiplying by the rms parameter value for this tensor + [for non-scalars]; this is appropriate because our update is scaled + by this quantity. + betas: beta1,beta2 are momentum constants for regular momentum, and moving sum-sq grad. + Must satisfy 0 < beta <= beta2 < 1. + scalar_lr_scale: A scaling factor on the learning rate, that we use to update the + scale of each parameter tensor and scalar parameters of the mode.. + If each parameter were decomposed + as p * p_scale.exp(), where (p**2).mean().sqrt() == 1.0, scalar_lr_scale + would be a the scaling factor on the learning rate of p_scale. + eps: A general-purpose epsilon to prevent division by zero + param_min_rms: Minimum root-mean-square value of parameter tensor, for purposes of + learning the scale on the parameters (we'll constrain the rms of each non-scalar + parameter tensor to be >= this value) + param_max_rms: Maximum root-mean-square value of parameter tensor, for purposes of + learning the scale on the parameters (we'll constrain the rms of each non-scalar + parameter tensor to be <= this value) + scalar_max: Maximum absolute value for scalar parameters (applicable if your + model has any parameters with numel() == 1). + size_update_period: The periodicity, in steps, with which we update the size (scale) + of the parameter tensor. This is provided to save a little time + in the update. + clipping_update_period: if clipping_scale is specified, this is the period + """ + + def __init__( + self, + params, + lr=3e-02, + clipping_scale=None, + betas=(0.9, 0.98), + scalar_lr_scale=0.1, + eps=1.0e-08, + param_min_rms=1.0e-05, + param_max_rms=3.0, + scalar_max=10.0, + size_update_period=4, + clipping_update_period=100, + parameters_names=None, + show_dominant_parameters=True, + ): + + assert parameters_names is not None, ( + "Please prepare parameters_names," + "which is a List[List[str]]. Each List[str] is for a group" + "and each str is for a parameter" + ) + defaults = dict( + lr=lr, + clipping_scale=clipping_scale, + betas=betas, + scalar_lr_scale=scalar_lr_scale, + eps=eps, + param_min_rms=param_min_rms, + param_max_rms=param_max_rms, + scalar_max=scalar_max, + size_update_period=size_update_period, + clipping_update_period=clipping_update_period, + ) + + super(ScaledAdam, self).__init__(params, defaults) + assert len(self.param_groups) == len(parameters_names) + self.parameters_names = parameters_names + self.show_dominant_parameters = show_dominant_parameters + + def __setstate__(self, state): + super(ScaledAdam, self).__setstate__(state) + + @torch.no_grad() + def step(self, closure=None): + """Performs a single optimization step. + + Arguments: + closure (callable, optional): A closure that reevaluates the model + and returns the loss. + """ + loss = None + if closure is not None: + with torch.enable_grad(): + loss = closure() + + batch = True + + for group, group_params_names in zip( + self.param_groups, self.parameters_names + ): + + with self.batched_params( + group["params"], group_params_names + ) as batches: + + # batches is list of pairs (stacked_param, state). stacked_param is like + # a regular parameter, and will have a .grad, but the 1st dim corresponds to + # a stacking dim, it is not a real dim. + + if ( + len(batches[0][1]) == 0 + ): # if len(first state) == 0: not yet initialized + clipping_scale = 1 + else: + clipping_scale = self._get_clipping_scale(group, batches) + + for p, state, _ in batches: + # Perform optimization step. + # grad is not going to be None, we handled that when creating the batches. + grad = p.grad + if grad.is_sparse: + raise RuntimeError( + "ScaledAdam optimizer does not support sparse gradients" + ) + # State initialization + if len(state) == 0: + self._init_state(group, p, state) + + self._step_one_batch(group, p, state, clipping_scale) + + return loss + + def _init_state(self, group: dict, p: Tensor, state: dict): + """ + Initializes state dict for parameter 'p'. Assumes that dim 0 of tensor p + is actually the batch dimension, corresponding to batched-together + parameters of a given shape. + + + Args: + group: Dict to look up configuration values. + p: The parameter that we are initializing the state for + state: Dict from string to whatever state we are initializing + """ + size_update_period = group["size_update_period"] + + state["step"] = 0 + + kwargs = {"device": p.device, "dtype": p.dtype} + + # 'delta' implements conventional momentum. There are + # several different kinds of update going on, so rather than + # compute "exp_avg" like in Adam, we store and decay a + # parameter-change "delta", which combines all forms of + # update. this is equivalent to how it's done in Adam, + # except for the first few steps. + state["delta"] = torch.zeros_like( + p, memory_format=torch.preserve_format + ) + + batch_size = p.shape[0] + numel = p.numel() // batch_size + numel = p.numel() + + if numel > 1: + # "param_rms" just periodically records the scalar root-mean-square value of + # the parameter tensor. + # it has a shape like (batch_size, 1, 1, 1, 1) + param_rms = ( + (p ** 2).mean(dim=list(range(1, p.ndim)), keepdim=True).sqrt() + ) + state["param_rms"] = param_rms + + state["scale_exp_avg_sq"] = torch.zeros_like(param_rms) + state["scale_grads"] = torch.zeros( + size_update_period, *param_rms.shape, **kwargs + ) + + # exp_avg_sq is the weighted sum of scaled gradients. as in Adam. + state["exp_avg_sq"] = torch.zeros_like( + p, memory_format=torch.preserve_format + ) + + def _get_clipping_scale( + self, group: dict, tuples: List[Tuple[Tensor, dict, List[str]]] + ) -> float: + """ + Returns a scalar factor <= 1.0 that dictates gradient clipping, i.e. we will scale the gradients + by this amount before applying the rest of the update. + + Args: + group: the parameter group, an item in self.param_groups + tuples: a list of tuples of (param, state, param_names) + where param is a batched set of parameters, + with a .grad (1st dim is batch dim) + and state is the state-dict where optimization parameters are kept. + param_names is a List[str] while each str is name for a parameter + in batched set of parameters "param". + """ + assert len(tuples) >= 1 + clipping_scale = group["clipping_scale"] + (first_p, first_state, _) = tuples[0] + step = first_state["step"] + if clipping_scale is None or step == 0: + # no clipping. return early on step == 0 because the other + # parameters' state won't have been initialized yet. + return 1.0 + clipping_update_period = group["clipping_update_period"] + + tot_sumsq = torch.tensor(0.0, device=first_p.device) + for (p, state, param_names) in tuples: + grad = p.grad + if grad.is_sparse: + raise RuntimeError( + "ScaledAdam optimizer does not support sparse gradients" + ) + if p.numel() == p.shape[0]: # a batch of scalars + tot_sumsq += ( + grad ** 2 + ).sum() # sum() to change shape [1] to [] + else: + tot_sumsq += ((grad * state["param_rms"]) ** 2).sum() + + tot_norm = tot_sumsq.sqrt() + if "model_norms" not in first_state: + first_state["model_norms"] = torch.zeros( + clipping_update_period, device=p.device + ) + first_state["model_norms"][step % clipping_update_period] = tot_norm + + if step % clipping_update_period == 0: + # Print some stats. + # We don't reach here if step == 0 because we would have returned + # above. + sorted_norms = first_state["model_norms"].sort()[0].to("cpu") + quartiles = [] + for n in range(0, 5): + index = min( + clipping_update_period - 1, + (clipping_update_period // 4) * n, + ) + quartiles.append(sorted_norms[index].item()) + + median = quartiles[2] + threshold = clipping_scale * median + first_state["model_norm_threshold"] = threshold + percent_clipped = ( + first_state["num_clipped"] * 100.0 / clipping_update_period + if "num_clipped" in first_state + else 0.0 + ) + first_state["num_clipped"] = 0 + quartiles = " ".join(["%.3e" % x for x in quartiles]) + logging.info( + f"Clipping_scale={clipping_scale}, grad-norm quartiles {quartiles}, " + f"threshold={threshold:.3e}, percent-clipped={percent_clipped:.1f}" + ) + + if step < clipping_update_period: + return 1.0 # We have not yet estimated a norm to clip to. + else: + try: + model_norm_threshold = first_state["model_norm_threshold"] + except KeyError: + logging.info( + "Warning: model_norm_threshold not in state: possibly " + "you changed config when restarting, adding clipping_scale option?" + ) + return 1.0 + ans = min(1.0, (model_norm_threshold / (tot_norm + 1.0e-20)).item()) + if ans < 1.0: + first_state["num_clipped"] += 1 + if ans < 0.1: + logging.warn( + f"Scaling gradients by {ans}, model_norm_threshold={model_norm_threshold}" + ) + if self.show_dominant_parameters: + assert p.shape[0] == len(param_names) + self._show_gradient_dominating_parameter(tuples, tot_sumsq) + return ans + + def _show_gradient_dominating_parameter( + self, tuples: List[Tuple[Tensor, dict, List[str]]], tot_sumsq: Tensor + ): + """ + Show information of parameter wihch dominanting tot_sumsq. + + Args: + tuples: a list of tuples of (param, state, param_names) + where param is a batched set of parameters, + with a .grad (1st dim is batch dim) + and state is the state-dict where optimization parameters are kept. + param_names is a List[str] while each str is name for a parameter + in batched set of parameters "param". + tot_sumsq: sumsq of all parameters. Though it's could be calculated + from tuples, we still pass it to save some time. + """ + all_sumsq_orig = {} + for (p, state, batch_param_names) in tuples: + # p is a stacked batch parameters. + batch_grad = p.grad + if p.numel() == p.shape[0]: # a batch of scalars + batch_sumsq_orig = batch_grad ** 2 + # Dummpy values used by following `zip` statement. + batch_rms_orig = torch.ones(p.shape[0]) + else: + batch_rms_orig = state["param_rms"] + batch_sumsq_orig = ((batch_grad * batch_rms_orig) ** 2).sum( + dim=list(range(1, batch_grad.ndim)) + ) + + for name, sumsq_orig, rms, grad in zip( + batch_param_names, batch_sumsq_orig, batch_rms_orig, batch_grad + ): + + proportion_orig = sumsq_orig / tot_sumsq + all_sumsq_orig[name] = (proportion_orig, sumsq_orig, rms, grad) + + assert torch.isclose( + sum([value[0] for value in all_sumsq_orig.values()]).cpu(), + torch.tensor(1.0), + ) + sorted_by_proportion = { + k: v + for k, v in sorted( + all_sumsq_orig.items(), + key=lambda item: item[1][0], + reverse=True, + ) + } + dominant_param_name = next(iter(sorted_by_proportion)) + ( + dominant_proportion, + dominant_sumsq, + dominant_rms, + dominant_grad, + ) = sorted_by_proportion[dominant_param_name] + logging.info( + f"Parameter Dominanting tot_sumsq {dominant_param_name}" + f" with proportion {dominant_proportion:.2f}," + f" where dominant_sumsq=(grad_sumsq*orig_rms_sq)" + f"={dominant_sumsq:.3e}," + f" grad_sumsq = {(dominant_grad**2).sum():.3e}," + f" orig_rms_sq={(dominant_rms**2).item():.3e}" + ) + + def _step_one_batch( + self, group: dict, p: Tensor, state: dict, clipping_scale: float + ): + """ + Do the step for one parameter, which is actually going to be a batch of + `real` parameters, with dim 0 as the batch dim. + Args: + group: dict to look up configuration values + p: parameter to update (actually multiple parameters stacked together + as a batch) + state: state-dict for p, to look up the optimizer state + """ + lr = group["lr"] + size_update_period = group["size_update_period"] + beta1 = group["betas"][0] + + grad = p.grad + if clipping_scale != 1.0: + grad = grad * clipping_scale + step = state["step"] + delta = state["delta"] + + delta.mul_(beta1) + batch_size = p.shape[0] + numel = p.numel() // batch_size + if numel > 1: + # Update the size/scale of p, and set param_rms + scale_grads = state["scale_grads"] + scale_grads[step % size_update_period] = (p * grad).sum( + dim=list(range(1, p.ndim)), keepdim=True + ) + if step % size_update_period == size_update_period - 1: + param_rms = state["param_rms"] # shape: (batch_size, 1, 1, ..) + param_rms.copy_( + (p ** 2) + .mean(dim=list(range(1, p.ndim)), keepdim=True) + .sqrt() + ) + if step > 0: + # self._size_update() learns the overall scale on the + # parameter, by shrinking or expanding it. + self._size_update(group, scale_grads, p, state) + + if numel == 1: + # For parameters with 1 element we just use regular Adam. + # Updates delta. + self._step_scalar(group, p, state) + else: + self._step(group, p, state) + + state["step"] = step + 1 + + def _size_update( + self, group: dict, scale_grads: Tensor, p: Tensor, state: dict + ) -> None: + """ + Called only where p.numel() > 1, this updates the scale of the parameter. + If we imagine: p = underlying_param * scale.exp(), and we are doing + gradient descent on underlying param and on scale, this function does the update + on `scale`. + + Args: + group: dict to look up configuration values + scale_grads: a tensor of shape (size_update_period, batch_size, 1, 1,...) containing + grads w.r.t. the scales. + p: The parameter to update + state: The state-dict of p + """ + + param_rms = state["param_rms"] + beta1, beta2 = group["betas"] + size_lr = group["lr"] * group["scalar_lr_scale"] + param_min_rms = group["param_min_rms"] + param_max_rms = group["param_max_rms"] + eps = group["eps"] + step = state["step"] + batch_size = p.shape[0] + + size_update_period = scale_grads.shape[0] + # correct beta2 for the size update period: we will have + # faster decay at this level. + beta2_corr = beta2 ** size_update_period + + scale_exp_avg_sq = state[ + "scale_exp_avg_sq" + ] # shape: (batch_size, 1, 1, ..) + scale_exp_avg_sq.mul_(beta2_corr).add_( + (scale_grads ** 2).mean( + dim=0 + ), # mean over dim `size_update_period` + alpha=1 - beta2_corr, + ) # shape is (batch_size, 1, 1, ...) + + # The 1st time we reach here is when size_step == 1. + size_step = (step + 1) // size_update_period + bias_correction2 = 1 - beta2_corr ** size_step + # we don't bother with bias_correction1; this will help prevent divergence + # at the start of training. + + denom = scale_exp_avg_sq.sqrt() + eps + + scale_step = ( + -size_lr + * (bias_correction2 ** 0.5) + * scale_grads.sum(dim=0) + / denom + ) + + is_too_small = param_rms < param_min_rms + is_too_large = param_rms > param_max_rms + + # when the param gets too small, just don't shrink it any further. + scale_step.masked_fill_(is_too_small, 0.0) + # when it gets too large, stop it from getting any larger. + scale_step.masked_fill_(is_too_large, -size_lr * size_update_period) + delta = state["delta"] + # the factor of (1-beta1) relates to momentum. + delta.add_(p * scale_step, alpha=(1 - beta1)) + + def _step(self, group: dict, p: Tensor, state: dict): + """ + This function does the core update of self.step(), in the case where the members of + the batch have more than 1 element. + + Args: + group: A dict which will be used to look up configuration values + p: The parameter to be updated + grad: The grad of p + state: The state-dict corresponding to parameter p + + This function modifies p. + """ + grad = p.grad + lr = group["lr"] + beta1, beta2 = group["betas"] + eps = group["eps"] + param_min_rms = group["param_min_rms"] + step = state["step"] + + exp_avg_sq = state["exp_avg_sq"] + exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=(1 - beta2)) + + this_step = state["step"] - ( + state["zero_step"] if "zero_step" in state else 0 + ) + bias_correction2 = 1 - beta2 ** (this_step + 1) + if bias_correction2 < 0.99: + # note: not in-place. + exp_avg_sq = exp_avg_sq * (1.0 / bias_correction2) + + denom = exp_avg_sq.sqrt() + denom += eps + grad = grad / denom + + alpha = -lr * (1 - beta1) * state["param_rms"].clamp(min=param_min_rms) + + delta = state["delta"] + delta.add_(grad * alpha) + p.add_(delta) + + def _step_scalar(self, group: dict, p: Tensor, state: dict): + """ + A simplified form of the core update for scalar tensors, where we cannot get a good + estimate of the parameter rms. + """ + beta1, beta2 = group["betas"] + scalar_max = group["scalar_max"] + eps = group["eps"] + lr = group["lr"] * group["scalar_lr_scale"] + grad = p.grad + + exp_avg_sq = state["exp_avg_sq"] # shape: (batch_size,) + exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2) + + # bias_correction2 is like in Adam. Don't bother with bias_correction1; + # slower update at the start will help stability anyway. + bias_correction2 = 1 - beta2 ** (state["step"] + 1) + denom = (exp_avg_sq / bias_correction2).sqrt() + eps + + delta = state["delta"] + delta.add_(grad / denom, alpha=-lr * (1 - beta1)) + p.clamp_(min=-scalar_max, max=scalar_max) + p.add_(delta) + + +class LRScheduler(object): + """ + Base-class for learning rate schedulers where the learning-rate depends on both the + batch and the epoch. + """ + + def __init__(self, optimizer: Optimizer, verbose: bool = False): + # Attach optimizer + if not isinstance(optimizer, Optimizer): + raise TypeError( + "{} is not an Optimizer".format(type(optimizer).__name__) + ) + self.optimizer = optimizer + self.verbose = verbose + + for group in optimizer.param_groups: + group.setdefault("base_lr", group["lr"]) + + self.base_lrs = [group["base_lr"] for group in optimizer.param_groups] + + self.epoch = 0 + self.batch = 0 + + def state_dict(self): + """Returns the state of the scheduler as a :class:`dict`. + + It contains an entry for every variable in self.__dict__ which + is not the optimizer. + """ + return { + "base_lrs": self.base_lrs, + "epoch": self.epoch, + "batch": self.batch, + } + + def load_state_dict(self, state_dict): + """Loads the schedulers state. + + Args: + state_dict (dict): scheduler state. Should be an object returned + from a call to :meth:`state_dict`. + """ + self.__dict__.update(state_dict) + + def get_last_lr(self) -> List[float]: + """Return last computed learning rate by current scheduler. Will be a list of float.""" + return self._last_lr + + def get_lr(self): + # Compute list of learning rates from self.epoch and self.batch and + # self.base_lrs; this must be overloaded by the user. + # e.g. return [some_formula(self.batch, self.epoch, base_lr) for base_lr in self.base_lrs ] + raise NotImplementedError + + def step_batch(self, batch: Optional[int] = None) -> None: + # Step the batch index, or just set it. If `batch` is specified, it + # must be the batch index from the start of training, i.e. summed over + # all epochs. + # You can call this in any order; if you don't provide 'batch', it should + # of course be called once per batch. + if batch is not None: + self.batch = batch + else: + self.batch = self.batch + 1 + self._set_lrs() + + def step_epoch(self, epoch: Optional[int] = None): + # Step the epoch index, or just set it. If you provide the 'epoch' arg, + # you should call this at the start of the epoch; if you don't provide the 'epoch' + # arg, you should call it at the end of the epoch. + if epoch is not None: + self.epoch = epoch + else: + self.epoch = self.epoch + 1 + self._set_lrs() + + def _set_lrs(self): + values = self.get_lr() + assert len(values) == len(self.optimizer.param_groups) + + for i, data in enumerate(zip(self.optimizer.param_groups, values)): + param_group, lr = data + param_group["lr"] = lr + self.print_lr(self.verbose, i, lr) + self._last_lr = [group["lr"] for group in self.optimizer.param_groups] + + def print_lr(self, is_verbose, group, lr): + """Display the current learning rate.""" + if is_verbose: + logging.info( + f"Epoch={self.epoch}, batch={self.batch}: adjusting learning rate" + f" of group {group} to {lr:.4e}." + ) + + +class Eden(LRScheduler): + """ + Eden scheduler. + The basic formula (before warmup) is: + lr = base_lr * (((batch**2 + lr_batches**2) / lr_batches**2) ** -0.25 * + (((epoch**2 + lr_epochs**2) / lr_epochs**2) ** -0.25)) * warmup + where `warmup` increases from linearly 0.5 to 1 over `warmup_batches` batches + and then stays constant at 1. + + + E.g. suggest base_lr = 0.04 (passed to optimizer) if used with ScaledAdam + + Args: + optimizer: the optimizer to change the learning rates on + lr_batches: the number of batches after which we start significantly + decreasing the learning rate, suggest 5000. + lr_epochs: the number of epochs after which we start significantly + decreasing the learning rate, suggest 6 if you plan to do e.g. + 20 to 40 epochs, but may need smaller number if dataset is huge + and you will do few epochs. + """ + + def __init__( + self, + optimizer: Optimizer, + lr_batches: Union[int, float], + lr_epochs: Union[int, float], + warmup_batches: Union[int, float] = 500.0, + verbose: bool = False, + ): + super(Eden, self).__init__(optimizer, verbose) + self.lr_batches = lr_batches + self.lr_epochs = lr_epochs + self.warmup_batches = warmup_batches + + def get_lr(self): + factor = ( + (self.batch ** 2 + self.lr_batches ** 2) / self.lr_batches ** 2 + ) ** -0.25 * ( + ((self.epoch ** 2 + self.lr_epochs ** 2) / self.lr_epochs ** 2) + ** -0.25 + ) + warmup_factor = ( + 1.0 + if self.batch >= self.warmup_batches + else 0.5 + 0.5 * (self.batch / self.warmup_batches) + ) + + return [x * factor * warmup_factor for x in self.base_lrs] + + +def _test_eden(): + m = torch.nn.Linear(100, 100) + optim = ScaledAdam(m.parameters(), lr=0.03) + + scheduler = Eden(optim, lr_batches=100, lr_epochs=2, verbose=True) + + for epoch in range(10): + scheduler.step_epoch(epoch) # sets epoch to `epoch` + + for step in range(20): + x = torch.randn(200, 100).detach() + x.requires_grad = True + y = m(x) + dy = torch.randn(200, 100).detach() + f = (y * dy).sum() + f.backward() + + optim.step() + scheduler.step_batch() + optim.zero_grad() + + logging.info(f"last lr = {scheduler.get_last_lr()}") + logging.info(f"state dict = {scheduler.state_dict()}") + + +# This is included mostly as a baseline for ScaledAdam. +class Eve(Optimizer): + """ + Implements Eve algorithm. This is a modified version of AdamW with a special + way of setting the weight-decay / shrinkage-factor, which is designed to make the + rms of the parameters approach a particular target_rms (default: 0.1). This is + for use with networks with 'scaled' versions of modules (see scaling.py), which + will be close to invariant to the absolute scale on the parameter matrix. + + The original Adam algorithm was proposed in `Adam: A Method for Stochastic Optimization`_. + The AdamW variant was proposed in `Decoupled Weight Decay Regularization`_. + Eve is unpublished so far. + + Arguments: + params (iterable): iterable of parameters to optimize or dicts defining + parameter groups + lr (float, optional): learning rate (default: 1e-3) + betas (Tuple[float, float], optional): coefficients used for computing + running averages of gradient and its square (default: (0.9, 0.999)) + eps (float, optional): term added to the denominator to improve + numerical stability (default: 1e-8) + weight_decay (float, optional): weight decay coefficient (default: 3e-4; + this value means that the weight would decay significantly after + about 3k minibatches. Is not multiplied by learning rate, but + is conditional on RMS-value of parameter being > target_rms. + target_rms (float, optional): target root-mean-square value of + parameters, if they fall below this we will stop applying weight decay. + + + .. _Adam: A Method for Stochastic Optimization: + https://arxiv.org/abs/1412.6980 + .. _Decoupled Weight Decay Regularization: + https://arxiv.org/abs/1711.05101 + .. _On the Convergence of Adam and Beyond: + https://openreview.net/forum?id=ryQu7f-RZ + """ + + def __init__( + self, + params, + lr=1e-3, + betas=(0.9, 0.98), + eps=1e-8, + weight_decay=1e-3, + target_rms=0.1, + ): + if not 0.0 <= lr: + raise ValueError("Invalid learning rate: {}".format(lr)) + if not 0.0 <= eps: + raise ValueError("Invalid epsilon value: {}".format(eps)) + if not 0.0 <= betas[0] < 1.0: + raise ValueError( + "Invalid beta parameter at index 0: {}".format(betas[0]) + ) + if not 0.0 <= betas[1] < 1.0: + raise ValueError( + "Invalid beta parameter at index 1: {}".format(betas[1]) + ) + if not 0 <= weight_decay <= 0.1: + raise ValueError( + "Invalid weight_decay value: {}".format(weight_decay) + ) + if not 0 < target_rms <= 10.0: + raise ValueError("Invalid target_rms value: {}".format(target_rms)) + defaults = dict( + lr=lr, + betas=betas, + eps=eps, + weight_decay=weight_decay, + target_rms=target_rms, + ) + super(Eve, self).__init__(params, defaults) + + def __setstate__(self, state): + super(Eve, self).__setstate__(state) + + @torch.no_grad() + def step(self, closure=None): + """Performs a single optimization step. + + Arguments: + closure (callable, optional): A closure that reevaluates the model + and returns the loss. + """ + loss = None + if closure is not None: + with torch.enable_grad(): + loss = closure() + + for group in self.param_groups: + for p in group["params"]: + if p.grad is None: + continue + + # Perform optimization step + grad = p.grad + if grad.is_sparse: + raise RuntimeError( + "AdamW does not support sparse gradients" + ) + + state = self.state[p] + + # State initialization + if len(state) == 0: + state["step"] = 0 + # Exponential moving average of gradient values + state["exp_avg"] = torch.zeros_like( + p, memory_format=torch.preserve_format + ) + # Exponential moving average of squared gradient values + state["exp_avg_sq"] = torch.zeros_like( + p, memory_format=torch.preserve_format + ) + + exp_avg, exp_avg_sq = state["exp_avg"], state["exp_avg_sq"] + + beta1, beta2 = group["betas"] + + state["step"] += 1 + bias_correction1 = 1 - beta1 ** state["step"] + bias_correction2 = 1 - beta2 ** state["step"] + + # Decay the first and second moment running average coefficient + exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1) + exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2) + denom = (exp_avg_sq.sqrt() * (bias_correction2 ** -0.5)).add_( + group["eps"] + ) + + step_size = group["lr"] / bias_correction1 + target_rms = group["target_rms"] + weight_decay = group["weight_decay"] + + if p.numel() > 1: + # avoid applying this weight-decay on "scaling factors" + # (which are scalar). + is_above_target_rms = p.norm() > ( + target_rms * (p.numel() ** 0.5) + ) + p.mul_(1 - (weight_decay * is_above_target_rms)) + + p.addcdiv_(exp_avg, denom, value=-step_size) + + # if random.random() < 0.0005: + # step = (exp_avg / denom) * step_size + # logging.info( + # f"Delta rms = {(step**2).mean().item()}, shape = {step.shape}" + # ) + + return loss + +def ScaledLinear(*args, initial_scale: float = 1.0, **kwargs) -> nn.Linear: + """ + Behaves like a constructor of a modified version of nn.Linear + that gives an easy way to set the default initial parameter scale. + + Args: + Accepts the standard args and kwargs that nn.Linear accepts + e.g. in_features, out_features, bias=False. + + initial_scale: you can override this if you want to increase + or decrease the initial magnitude of the module's output + (affects the initialization of weight_scale and bias_scale). + Another option, if you want to do something like this, is + to re-initialize the parameters. + """ + ans = nn.Linear(*args, **kwargs) + with torch.no_grad(): + ans.weight[:] *= initial_scale + if ans.bias is not None: + torch.nn.init.uniform_( + ans.bias, -0.1 * initial_scale, 0.1 * initial_scale + ) + return ans +def _test_scaled_adam(hidden_dim: int): + import timeit + + E = 100 + B = 4 + T = 2 + logging.info("in test_eve_cain") + # device = torch.device('cuda') + device = torch.device("cpu") + dtype = torch.float32 + + # these input_magnitudes and output_magnitudes are to test that + # Abel is working as we expect and is able to adjust scales of + # different dims differently. + input_magnitudes = (1.0 * torch.randn(E, dtype=dtype, device=device)).exp() + output_magnitudes = (1.0 * torch.randn(E, dtype=dtype, device=device)).exp() + + for iter in [1, 0]: + Linear = torch.nn.Linear if iter == 0 else ScaledLinear + + m = torch.nn.Sequential( + Linear(E, hidden_dim), + torch.nn.PReLU(), + Linear(hidden_dim, hidden_dim), + torch.nn.PReLU(), + Linear(hidden_dim, E), + ).to(device) + + train_pairs = [ + ( + 100.0 + * torch.randn(B, T, E, device=device, dtype=dtype) + * input_magnitudes, + torch.randn(B, T, E, device=device, dtype=dtype) + * output_magnitudes, + ) + for _ in range(20) + ] + + if iter == 0: + optim = Eve(m.parameters(), lr=0.003) + elif iter == 1: + optim = ScaledAdam(m.parameters(), lr=0.03, clipping_scale=2.0) + scheduler = Eden(optim, lr_batches=200, lr_epochs=5, verbose=False) + + start = timeit.default_timer() + avg_loss = 0.0 + for epoch in range(180): + scheduler.step_epoch() + # if epoch == 100 and iter in [2,3]: + # optim.reset_speedup() # check it doesn't crash. + + # if epoch == 130: + # opts = diagnostics.TensorDiagnosticOptions( + # 2 ** 22 + # ) # allow 4 megabytes per sub-module + # diagnostic = diagnostics.attach_diagnostics(m, opts) + + for n, (x, y) in enumerate(train_pairs): + y_out = m(x) + loss = ((y_out - y) ** 2).mean() * 100.0 + if epoch == 0 and n == 0: + avg_loss = loss.item() + else: + avg_loss = 0.98 * avg_loss + 0.02 * loss.item() + if n == 0 and epoch % 5 == 0: + # norm1 = '%.2e' % (m[0].weight**2).mean().sqrt().item() + # norm1b = '%.2e' % (m[0].bias**2).mean().sqrt().item() + # norm2 = '%.2e' % (m[2].weight**2).mean().sqrt().item() + # norm2b = '%.2e' % (m[2].bias**2).mean().sqrt().item() + # scale1 = '%.2e' % (m[0].weight_scale.exp().item()) + # scale1b = '%.2e' % (m[0].bias_scale.exp().item()) + # scale2 = '%.2e' % (m[2].weight_scale.exp().item()) + # scale2b = '%.2e' % (m[2].bias_scale.exp().item()) + lr = scheduler.get_last_lr()[0] + logging.info( + f"Iter {iter}, epoch {epoch}, batch {n}, avg_loss {avg_loss:.4g}, lr={lr:.4e}" + ) # , norms={norm1,norm1b,norm2,norm2b}") # scales={scale1,scale1b,scale2,scale2b} + loss.log().backward() + optim.step() + optim.zero_grad() + scheduler.step_batch() + + # diagnostic.print_diagnostics() + + stop = timeit.default_timer() + logging.info(f"Iter={iter}, Time taken: {stop - start}") + + logging.info(f"last lr = {scheduler.get_last_lr()}") + # logging.info("state dict = ", scheduler.state_dict()) + # logging.info("optim state_dict = ", optim.state_dict()) + logging.info(f"input_magnitudes = {input_magnitudes}") + logging.info(f"output_magnitudes = {output_magnitudes}") + + +if __name__ == "__main__": + torch.set_num_threads(1) + torch.set_num_interop_threads(1) + logging.getLogger().setLevel(logging.INFO) + import subprocess + + s = subprocess.check_output( + "git status -uno .; git log -1; git diff HEAD .", shell=True + ) + logging.info(s) + import sys + + if len(sys.argv) > 1: + hidden_dim = int(sys.argv[1]) + else: + hidden_dim = 200 + + _test_scaled_adam(hidden_dim) + _test_eden() diff --git a/steps/trainer.py b/steps/trainer.py new file mode 100644 index 0000000..0cce5de --- /dev/null +++ b/steps/trainer.py @@ -0,0 +1,467 @@ +import time +import os, random +import torch +import math, pickle +from tqdm import tqdm +from torch.optim import AdamW +from torch.optim.lr_scheduler import LambdaLR +import torch.nn as nn +import torch.distributed as dist +from torch.utils.tensorboard import SummaryWriter +import numpy as np +from torch.utils.data.distributed import DistributedSampler +import logging +from data import gigaspeech +from models import voicecraft + +from .trainer_utils import DistributedDynamicBatchSampler, StatefulDistributedSampler, AverageMeter, print_model_info +from .optim import ScaledAdam, Eden + + +class Trainer: + + def __init__(self, args, world_size, rank): + self.start_time = time.time() + self.args = args + self.world_size, self.rank = world_size, rank + self.device = torch.device(f"cuda:{rank}" if torch.cuda.is_available() else "cpu") + if self.rank == 0: + self.writer = SummaryWriter(args.exp_dir) + self.seed_everything(seed=self.args.seed) + self.meters = self._setup_meters() + + self.progress, self.total_progress = self._setup_progress() + + self.model, self.trainables, self.optim_states, self.scheduler_states = self._setup_models() + + self.train_dataset_length, self.train_sampler, self.train_loader, self.valid_loader = self._setup_dataloader() + if self.args.num_steps != None: + self.total_step = self.args.num_steps + self.args.num_epochs = math.ceil(self.total_step / math.floor(self.train_dataset_length / self.args.batch_size)) if not self.args.dynamic_batching else None + else: + self.total_step = int(math.floor(self.train_dataset_length / self.args.batch_size))*self.args.num_epochs + + self.optimizer, self.scheduler = self._setup_optimizer() + self.scaler = torch.cuda.amp.GradScaler() + self.model = torch.nn.parallel.DistributedDataParallel(self.model, device_ids=[self.rank], find_unused_parameters=False) + + if self.rank == 0: + self.early_stop_accu_steps = 0 + if self.args.dynamic_batching: + logging.info(f"max number of tokens per GPU in a training batch: {self.args.max_num_tokens}, max number of tokens per GPU in a inference batch: {self.args.val_max_num_tokens}") + else: + logging.info(f"batch size (summed over all GPUs): {self.args.batch_size}") + + def train(self): + flag = True + skip_flag = False + data_start_time = time.time() + while flag: + self.train_sampler.set_epoch(self.progress['epoch']) + for i, batch in enumerate(self.train_loader): + data_end_time = time.time() + self.model.train() + if self.progress['step'] > self.total_step: + flag = False + self.validate_and_save() + if self.rank == 0: + self.writer.close() + break + if isinstance(self.scheduler, Eden): + self.scheduler.step_epoch(self.progress['step']//self.args.pseudo_epoch_size + 1) + if self.args.optimizer_name == "ScaledAdam": + cur_lr = self.scheduler.get_last_lr()[0] + else: + lrs = [param_group['lr'] for param_group in self.optimizer.param_groups] + assert lrs[0] == lrs[1] + cur_lr = lrs[0] + + if self.rank == 0 and self.progress['step'] % self.args.tb_write_every_n_steps == 0: + self.writer.add_scalar("train/lr", cur_lr, self.progress['step']) + self.wandb.log({"train/lr": cur_lr}, step=self.progress['step']) + + all_inds = list(range(len(batch['y']))) + sum_losses = 0 + sum_top10acc = 0 + sum_ntoken = 0 + sum_top10acc_cbi = [0 for _ in range(self.args.n_codebooks)] + for j in range(self.args.gradient_accumulation_steps): + cur_ind = all_inds[j::self.args.gradient_accumulation_steps] + cur_batch = {key: batch[key][cur_ind] for key in batch} + with torch.cuda.amp.autocast(dtype=torch.float16 if self.args.precision=="float16" else torch.float32): + out = self.model(cur_batch) + + record_loss = out['loss'].detach().to(self.rank) + top10acc = out['top10acc'].to(self.rank) + effective_ntoken = out['effective_ntoken'].to(self.rank) + is_nan = torch.tensor(int(torch.isnan(record_loss).any()), dtype=torch.float32, device=self.rank) + + dist.all_reduce(record_loss, op=dist.ReduceOp.SUM) + dist.all_reduce(top10acc, op=dist.ReduceOp.SUM) + dist.all_reduce(effective_ntoken, op=dist.ReduceOp.SUM) + dist.all_reduce(is_nan, op=dist.ReduceOp.SUM) + + # check if loss is nan + if is_nan.item() > 0: + logging.info(f"loss at step {self.progress['step']} is nan, therefore skip this batch") + skip_flag = True + continue + + sum_losses += record_loss.item() + sum_top10acc += top10acc.item() + sum_ntoken += effective_ntoken.item() + + if 'top10acc_by_codebook' in out: + for cb in range(self.args.n_codebooks): + top10acc_cbi = out['top10acc_by_codebook'][cb] + dist.all_reduce(top10acc_cbi, op=dist.ReduceOp.SUM) + sum_top10acc_cbi[cb] += top10acc_cbi.item() + + if self.rank == 0: + average_loss = sum_losses / sum_ntoken + average_top10acc = sum_top10acc / sum_ntoken + self.meters['train_loss'].update(average_loss, batch['x'].shape[0]*self.world_size) + self.meters['train_top10acc'].update(average_top10acc, batch['x'].shape[0]*self.world_size) + self.meters['train_top10acc'].update(average_top10acc, batch['x'].shape[0]*self.world_size) + average_top10acc_cbi = [sum_top10acc_cbi[cb] / sum_ntoken * self.args.n_codebooks for cb in range(self.args.n_codebooks)] + for cb in range(self.args.n_codebooks): + self.meters[f'train_top10acc_cb{cb+1}'].update(average_top10acc_cbi[cb], batch['x'].shape[0]*self.world_size) + + if self.progress['step'] % self.args.tb_write_every_n_steps == 0: + self.writer.add_scalar('train/loss', average_loss, self.progress['step']) + self.writer.add_scalar('train/top10acc', average_top10acc, self.progress['step']) + self.writer.add_scalar("train/ntokens", sum_ntoken, self.progress['step']) + for cb in range(self.args.n_codebooks): + self.writer.add_scalar(f'train/top10acc_cb{cb+1}', average_top10acc_cbi[cb], self.progress['step']) + + if self.args.optimizer_name == "ScaledAdam": + self.scaler.scale(out['loss']).backward() + else: + self.scaler.scale(out['loss']/out['effective_ntoken']).backward() + + if skip_flag: + self.optimizer.zero_grad() + skip_flag = False + continue + + if self.args.optimizer_name != "ScaledAdam": + self.scaler.unscale_(self.optimizer) + torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.args.gradient_clip_val) + self.scaler.step(self.optimizer) + self.scaler.update() + + self.optimizer.zero_grad() + + if self.args.optimizer_name == "ScaledAdam": + self.scheduler.step_batch(self.progress['step']) + else: + self.scheduler.step() + + if self.rank == 0: + self.meters['data_time'].update(data_end_time - data_start_time) + self.meters['train_time'].update(time.time() - data_end_time) + if self.progress['step'] % self.args.tb_write_every_n_steps == 0: + self.writer.add_scalar("train/data_time", data_end_time - data_start_time, self.progress['step']) + self.writer.add_scalar("train/train_time", time.time() - data_end_time, self.progress['step']) + + + # logging + if self.progress['step'] % self.args.print_every_n_steps == 0: + log_out = {} + log_out['cur_epoch'] = f"{self.progress['epoch']}/{self.args.num_epochs}" if self.args.num_epochs is not None else f"{self.progress['epoch']}" + log_out['cur_step'] = f"{int(self.progress['cur_step']+1)}" + log_out['total_step'] = f"{self.progress['step']}/{self.args.num_steps}" + log_out['lr'] = f"{cur_lr:.7f}" + log_out['ntokens'] = f"{sum_ntoken}" + for key in self.meters: + if self.meters[key].val != 0 or self.meters[key].avg != 0: + log_out[key] = f"{self.meters[key].val:.4f} ({self.meters[key].avg:.4f})" if isinstance(self.meters[key].val, float) else f"{self.meters[key].val}" + logging.info(log_out) + if np.isnan(self.meters['train_loss'].avg): + logging.warning("training diverged...") + raise RuntimeError("training diverged...") + + # validation and save models + if self.progress['step'] % self.args.val_every_n_steps == 0: + dist.barrier() + self.validate_and_save() + + self.progress['step'] += 1 + self.progress['cur_step'] += 1 + + data_start_time = time.time() + self.progress['epoch'] += 1 + self.progress['cur_step'] = 0 # reset cur_step to be 0 + dist.destroy_process_group() + + def validate_and_save(self): + self.model.eval() + + score = self.validate(self.valid_loader) + + if self.rank == 0: + if self.args.early_stop_threshold > 0: + if self.progress['best_score'] - score < self.args.early_stop_threshold: + self.early_stop_accu_steps += self.args.val_every_n_steps + if self.early_stop_accu_steps >= self.args.early_stop_step-1: + logging.info(f"early stop based on self.args.early_stop_threshold: {self.args.early_stop_threshold}, and self.args.early_stop_step: {self.args.early_stop_step}") + logging.info(f"best validation score at step: {self.progress['best_step']}, and the score is {self.progress['best_score']:.4f}") + dist.destroy_process_group() + raise RuntimeError("early stop") + else: + self.early_stop_accu_steps = 0 + + if (score < self.progress['best_score']): + self.progress['best_step'] = self.progress['step'] + self.progress['best_score'] = score + save_path = os.path.join(self.args.exp_dir,"best_bundle.pth") + torch.save( + { + "model": self.model.module.state_dict(), + "optimizer": self.optimizer.state_dict(), + "scheduler": self.scheduler.state_dict(), + "config": self.args, + "phn2num": self.train_loader.dataset.phn2num + },save_path + ) + logging.info(f"save *best* models at {save_path} at global step {self.progress['step']}") + self._save_progress() + save_path = os.path.join(self.args.exp_dir,"bundle.pth") + torch.save( + { + "model": self.model.module.state_dict(), + "optimizer": self.optimizer.state_dict(), + "scheduler": self.scheduler.state_dict(), + "config": self.args, + "phn2num": self.train_loader.dataset.phn2num + },save_path + ) + logging.info(f"save models, indices, acc and other statistics at {save_path} and {self.args.exp_dir}/progress.pkl at global step {self.progress['step']}") + + dist.barrier() + + def validate(self, valid_loader=None, hide_progress=True): + if valid_loader == None: + valid_loader = self.valid_loader + self.model.eval() + + start_val_time = time.time() + sum_losses = 0 + sum_top10acc = 0 + sum_ntoken = 0 + sum_top10acc_cbi = [0 for _ in range(self.args.n_codebooks)] + + with torch.no_grad(): + for i, batch in enumerate(tqdm(valid_loader, disable=hide_progress)): + out = self.model(batch) + sum_losses += out['loss'] + sum_top10acc += out['top10acc'] + sum_ntoken += out['effective_ntoken'] + if 'top10acc_by_codebook' in out: + for cb in range(self.args.n_codebooks): + sum_top10acc_cbi[cb] += out['top10acc_by_codebook'][cb] + + dist.all_reduce(sum_losses, op=dist.ReduceOp.SUM) + dist.all_reduce(sum_top10acc, op=dist.ReduceOp.SUM) + dist.all_reduce(sum_ntoken, op=dist.ReduceOp.SUM) + + if 'top10acc_by_codebook' in out: + for cb in range(self.args.n_codebooks): + dist.all_reduce(sum_top10acc_cbi[cb], op=dist.ReduceOp.SUM) + + if self.rank == 0: + val_loss = sum_losses / sum_ntoken + val_top10acc = sum_top10acc / sum_ntoken + # logging + self.meters['val_loss'].update(val_loss) + logging.info(f"val loss: {val_loss:.5f}") + self.writer.add_scalar("val/loss", val_loss, self.progress['step']) + + self.meters['val_top10acc'].update(val_top10acc) + logging.info(f"val top10acc: {val_top10acc:.5f}") + self.writer.add_scalar("val/top10acc", val_top10acc, self.progress['step']) + for cb in range(self.args.n_codebooks): + average_top10acc_cbi = sum_top10acc_cbi[cb] / sum_ntoken * self.args.n_codebooks + self.meters[f'val_top10acc_cb{cb+1}'].update(average_top10acc_cbi) + self.writer.add_scalar(f'val/top10acc_cb{cb+1}', average_top10acc_cbi, self.progress['step']) + + logging.info(f"validation takes: {time.time() - start_val_time:.2f}s") + logging.info(f"Step [{self.progress['step']}/{self.total_step}]\t Time elapsed {(time.time() - self.start_time)/3600.:.2f}h, Val Loss: {val_loss:.4f}, Val Top10Acc: {val_top10acc:.4f}") + return val_loss.item() + else: + return None + + def _setup_meters(self): + meters = {} + meter_names = ['train_loss', 'val_loss', 'train_top10acc', 'val_top10acc', 'data_time', 'train_time'] + meter_names += ['train_dur_loss', 'train_dur_acc', 'val_dur_loss', 'val_dur_acc'] + meter_names += [f'train_top10acc_cb{cb+1}' for cb in range(self.args.n_codebooks)] + meter_names += [f'val_top10acc_cb{cb+1}' for cb in range(self.args.n_codebooks)] + for name in meter_names: + meters[name] = AverageMeter() + return meters + def _setup_progress(self): + progress = {} + progress['best_step'] = 1 + progress['best_score'] = np.inf # this records loss value + progress['step'] = 1 + progress['epoch'] = 1 + progress['cur_step'] = 0 # step in the current epoch, for resuming the sampler + total_progress = [] + # if self.args.resume or self.args.validate: + if self.args.resume: + progress_pkl = "%s/progress.pkl" % self.args.exp_dir + with open(progress_pkl, "rb") as f: + total_progress = pickle.load(f) + progress['best_step'], progress['best_score'], progress['step'], progress['epoch'], progress['cur_step'], _ = total_progress[-1] + if self.rank == 0: + logging.info("\nResume training from:") + logging.info(" epoch = %s" % progress['epoch']) + logging.info(" cur_step = %s" % progress['cur_step']) + logging.info(" step = %s" % progress['step']) + logging.info(" best_step = %s" % progress['best_step']) + logging.info(" best_score = %s" % progress['best_score']) + return progress, total_progress + + def _save_progress(self): + self.total_progress.append([self.progress['best_step'], self.progress['best_score'], int(self.progress['step']+1), self.progress['epoch'], int(self.progress['cur_step']+1), time.time() - self.start_time]) + with open("%s/progress.pkl" % self.args.exp_dir, "wb") as f: + pickle.dump(self.total_progress, f) + + def _setup_dataloader(self): + assert self.args.dataset == 'gigaspeech', "only gigaspeech is supported for now" + train_dataset, val_dataset = gigaspeech.dataset(self.args, 'train'), gigaspeech.dataset(self.args, 'validation') + + if self.args.dynamic_batching: + train_sampler = DistributedDynamicBatchSampler(train_dataset, self.args, num_replicas=self.world_size, rank=self.rank, shuffle=True, seed=self.args.seed, drop_last=True, lengths_list=train_dataset.lengths_list, verbose=True, epoch=0) + valid_sampler = DistributedDynamicBatchSampler(val_dataset, self.args, num_replicas=self.world_size, rank=self.rank, shuffle=True, seed=self.args.seed, drop_last=True, lengths_list=val_dataset.lengths_list, verbose=True, epoch=0) + else: + train_sampler = StatefulDistributedSampler(train_dataset, self.args.batch_size//self.world_size, num_replicas=self.world_size, rank=self.rank, shuffle=True, seed=self.args.seed, drop_last=True) + valid_sampler = DistributedSampler(val_dataset, num_replicas=self.world_size, rank=self.rank, shuffle=False, seed=self.args.seed, drop_last=False) + + if self.progress['step'] > 1: + train_sampler.set_epoch_resume(self.progress['epoch'], self.progress['cur_step']) + + if self.args.dynamic_batching: + train_loader = torch.utils.data.DataLoader(train_dataset, + batch_sampler=train_sampler, + num_workers=self.args.num_workers//self.world_size, + collate_fn=train_dataset.collate, persistent_workers=True + ) + valid_loader = torch.utils.data.DataLoader(val_dataset, + batch_sampler=valid_sampler, + num_workers=self.args.num_workers//self.world_size, + collate_fn=val_dataset.collate, persistent_workers=True + ) + else: + train_loader = torch.utils.data.DataLoader(train_dataset, + batch_size=self.args.batch_size//self.world_size, sampler=train_sampler, num_workers=self.args.num_workers//self.world_size, + collate_fn=train_dataset.collate, persistent_workers=True + ) + valid_loader = torch.utils.data.DataLoader(val_dataset, + batch_size=self.args.batch_size//self.world_size, sampler=valid_sampler, + num_workers=self.args.num_workers//self.world_size, + collate_fn=val_dataset.collate, persistent_workers=True + ) + return len(train_dataset), train_sampler, train_loader, valid_loader + + + + def _setup_models(self): + model = voicecraft.VoiceCraft(self.args) + + if self.rank == 0: + logging.info(model) + logging.info("model parameters") + print_model_info(model) + + if self.progress['step'] > 1: + bundle = torch.load(os.path.join(self.args.exp_dir, "bundle.pth"), map_location="cpu") + model.load_state_dict(bundle['model']) + optim_states = bundle['optimizer'] + scheduler_states = bundle['scheduler'] + if self.rank == 0: + logging.info("loaded parameters and data indices from epoch %d, global step %d" % (self.progress['epoch'], self.progress['step'])) + del bundle['model'] + else: + optim_states = None + scheduler_states = None + + if self.args.load_model_from != None and self.progress['step'] <= 1: + sd = torch.load(self.args.load_model_from, map_location="cpu")['model'] + model.load_state_dict(sd) + del sd + + if self.args.optimizer_name == "ScaledAdam": + trainables = [p for p in model.parameters() if p.requires_grad] + else: + no_decay = [".bias", ".audio_embeddings.weight", ".text_embeddings.weight", ".norm.weight", ".norm1.weight", ".norm2.weight"] + optimizer_grouped_parameters = [ + { + "params": [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay) and p.requires_grad], + "weight_decay": self.args.weight_decay, + }, + { + "params": [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay) and p.requires_grad], + "weight_decay": 0.0, + }, + ] + if len(optimizer_grouped_parameters[1]['params']) == 0: + logging.info("there is no embedding weights, bias, and layernorm parameters in the model, which should be True, check model parameter names") + trainables = optimizer_grouped_parameters[0] + else: + trainables = optimizer_grouped_parameters + model.to(self.device) + + return model, trainables, optim_states, scheduler_states + + + def _setup_optimizer(self): + if self.args.optimizer_name == "ScaledAdam": + parameters_names = [] + parameters_names.append([n for n,p in self.model.named_parameters() if p.requires_grad]) + optimizer = ScaledAdam( + self.trainables, + lr=self.args.lr, + betas=(0.9, 0.95), + clipping_scale=2.0, + parameters_names=parameters_names, + show_dominant_parameters=False, + clipping_update_period=self.args.clipping_update_period, + ) + scheduler = Eden(optimizer, self.args.reduce_lr_start_step, self.args.reduce_lr_start_epoch, warmup_batches=self.total_step * self.args.warmup_fraction) + + else: + optimizer = AdamW(self.trainables, lr=self.args.lr) + warmup_steps = self.total_step * self.args.warmup_fraction + def lr_lambda(current_step: int): + if current_step < warmup_steps: + return float(current_step) / float(max(1, warmup_steps)) + return max( + 0.0, float(self.total_step - current_step) / float(max(1, self.total_step - warmup_steps)) + ) + + scheduler = LambdaLR(optimizer, lr_lambda, last_epoch=-1) + + # if resume + if self.progress['step'] > 1: + optimizer.load_state_dict(self.optim_states) + for state in optimizer.state.values(): + for k, v in state.items(): + if isinstance(v, torch.Tensor): + state[k] = v.cuda() + del self.optim_states + + scheduler.load_state_dict(self.scheduler_states) + + optimizer.zero_grad() + return optimizer, scheduler + + def seed_everything(self, seed=1): + os.environ['PYTHONHASHSEED'] = str(seed) + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed(seed) + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True \ No newline at end of file diff --git a/steps/trainer_utils.py b/steps/trainer_utils.py new file mode 100644 index 0000000..65e2d14 --- /dev/null +++ b/steps/trainer_utils.py @@ -0,0 +1,628 @@ + +import torch +import math +import torch.distributed as dist +from torch.utils.data.sampler import Sampler +import copy +import numpy as np +from typing import List +from scipy.stats import lognorm +import logging + +class StatefulDistributedSampler(Sampler[int]): + def __init__(self, dataset, batch_size, num_replicas = None, rank = None, shuffle = True, seed = 0, drop_last = False): + if num_replicas is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + num_replicas = dist.get_world_size() + if rank is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + rank = dist.get_rank() + if rank >= num_replicas or rank < 0: + raise ValueError( + "Invalid rank {}, rank should be in the interval" + " [0, {}]".format(rank, num_replicas - 1)) + self.dataset = dataset + self.batch_size = batch_size + self.num_replicas = num_replicas + self.rank = rank + self.epoch = 0 + self.cur_epoch = 0 + self.drop_last = drop_last + # If the dataset length is evenly divisible by # of replicas, then there + # is no need to drop any data, since the dataset will be split equally. + if self.drop_last and len(self.dataset) % self.num_replicas != 0: # type: ignore[arg-type] + # Split to nearest available length that is evenly divisible. + # This is to ensure each rank receives the same amount of data when + # using this Sampler. + self.num_samples = math.ceil( + (len(self.dataset) - self.num_replicas) / self.num_replicas # type: ignore[arg-type] + ) + else: + self.num_samples = math.ceil(len(self.dataset) / self.num_replicas) # type: ignore[arg-type] + self.total_size = self.num_samples * self.num_replicas + self.shuffle = shuffle + self.seed = seed + self.continue_flag = False + def __len__(self): + return self.num_samples + + def set_epoch(self, epoch): + r""" + Sets the epoch for this sampler. When :attr:`shuffle=True`, this ensures all replicas + use a different random ordering for each epoch. Otherwise, the next iteration of this + sampler will yield the same ordering. + + Args: + epoch (int): Epoch number. + """ + self.epoch = epoch + + if self.shuffle: + # deterministically shuffle based on epoch and seed + g = torch.Generator() + g.manual_seed(self.seed + self.epoch) + indices = torch.randperm(len(self.dataset), generator=g).tolist() # type: ignore[arg-type] + else: + indices = list(range(len(self.dataset))) # type: ignore[arg-type] + + if not self.drop_last: + # add extra samples to make it evenly divisible + padding_size = self.total_size - len(indices) + if padding_size <= len(indices): + indices += indices[:padding_size] + else: + indices += (indices * math.ceil(padding_size / len(indices)))[:padding_size] + else: + # remove tail of data to make it evenly divisible. + indices = indices[:self.total_size] + assert len(indices) == self.total_size + + # subsample + indices = indices[self.rank:self.total_size:self.num_replicas] + assert len(indices) == self.num_samples + self.indices = indices + + if self.continue_flag: + self.indices = self.indices[int(self.cur_step*self.batch_size):] + self.num_samples = len(self.indices) + self.continue_flag = False + + def __iter__(self): + for idx in self.indices: + yield idx + + def set_epoch_resume(self, epoch, cur_step): + self.epoch = epoch + self.cur_step = cur_step + self.continue_flag = True + + +class StatefulSampler(Sampler): + def __init__(self, data_source_length, batch_size, use_random=True, seed=1, epoch=0): + self.use_random = use_random + self.data_source_length = data_source_length + self.num_samples = self.data_source_length + self.batch_size = batch_size + self.continue_flag = False + self.seed = seed + self.epoch = epoch + self.cur_step = 0 + + def __len__(self): + return self.num_samples + + def __iter__(self): + + for idx in self.indices: + yield idx + + def set_epoch(self, epoch): + self.epoch = epoch + if self.use_random: + # deterministically shuffle based on epoch and seed + g = torch.Generator() + g.manual_seed(self.seed + self.epoch) + self.indices = torch.randperm(self.data_source_length, generator=g).tolist() # type: ignore[arg-type] + else: + self.indices = list(range(self.data_source_length)) # type: ignore[arg-type] + if self.continue_flag == True: + self.continue_flag = False + self.indices = self.indices[int(self.cur_step*self.batch_size):] + + self.num_samples = len(self.indices) + + def set_epoch_resume(self, epoch, cur_step): + self.epoch = epoch + self.cur_step = cur_step + self.continue_flag = True + + +class AverageMeter: + """Computes and stores the average and current value""" + def __init__(self): + self.reset() + + def reset(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + + def update(self, val, n=1): + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / self.count + +def print_model_info(model, print_model = False, print_params = True): + if print_model: + logging.info(model) + if print_params: + all_params = {} + for name, p in model.named_parameters(): + name = name.split(".")[0] + if name in all_params: + all_params[name] += p.numel() + else: + all_params[name] = p.numel() + logging.info("num of parameters of each components:") + for name in all_params: + logging.info(f"{name}: {all_params[name]/1000000.:.2f}m") + + +class DistributedDynamicBatchSampler(Sampler): + """ + modified from SpeechBrian, https://github.com/speechbrain/speechbrain/blob/develop/speechbrain/dataio/sampler.py#L307 + This BatchSampler batches examples together by grouping them by their length. + + Every example in the batch have approximately the same length and + thus padding is minimized. + This enables faster training on datasets + where length of examples can vary significantly (e.g Librispeech). + Inspired by: https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length + + Dynamic batching is performed by specifying a max_batch_length which is the + upper limit for the sum of the length of examples in a batch: + e.g., if ex1 has length 4, ex2 length 5 and if max_batch_length is set to 6 + ex1 and ex2 will be placed, alone, in two distinct batches. + + Length for each example can be obtained in two manners. + If the input dataset is a DynamicItemDataset it can be obtained by specifying a + length_func. Default assumes a "duration" entry is in the annotation. + Length for each example can also be passed to this class upon instantiation + by specifying a list containing the length for each example and passing it to + lengths_list. + + Examples are grouped together by defining a set of possible discrete intervals + (buckets). Examples whose length fall into these intervals can be batched together. + + The number of buckets can be specified by using the arg num_buckets. + There is usually an optimal range for the value of this argument. + + If num_buckets == 1, all examples can be batched together. You have maximum randomization + but your training speed will be slower due to the fact that a large amount of the values will be padding + as long and short examples can be batched together. + As the number of buckets grows only examples with similar + length can be grouped together. + This trades-off speed with randomization. + TLDR: Low number -> better randomization, High number -> faster training. + NOTE THAT: if set too high the training speed will decrease. If num_buckets -> number of examples in the dataset the batch size + will be small impacting training speed and possibly performance. + + The buckets can also be specified by passing a list to the bucket_boundaries + argument instead of specifying a left_bucket_length and a bucket_length_multiplier. + + Example + ------- + >>> import torch + >>> import speechbrain as sb + >>> from speechbrain.dataio.sampler import DynamicBatchSampler + >>> from speechbrain.dataio.dataset import DynamicItemDataset + >>> from speechbrain.dataio.dataloader import SaveableDataLoader + >>> from speechbrain.dataio.batch import PaddedBatch + >>> import numpy as np + >>> item_lengths = sorted([np.random.randint(10, 100) for x in range(20)]) + >>> dataset = {"ex_{}".format(x) : {"wav" :torch.randn(x)} for x in item_lengths} + >>> dataset = DynamicItemDataset(dataset) + >>> dataset.set_output_keys(["wav"]) + >>> length_func = lambda x : len(x) # trivial in this example + >>> bsampler = DynamicBatchSampler(dataset, 20, 4, length_func, shuffle=False, batch_ordering='descending') + >>> dataloader = SaveableDataLoader(dataset, batch_sampler=bsampler, collate_fn=PaddedBatch) + >>> for i, b in enumerate(dataloader): + ... data, length = b["wav"] + >>> assert data.shape[-1] == max(item_lengths) + + Arguments + --------- + dataset : torch.utils.data.Dataset + Pytorch Dataset from which elements will be sampled. + max_batch_length : int + Upper limit for the sum of the length of examples in a batch. + Should be chosen based on your GPU memory. + num_buckets : int + Number of discrete buckets used to group examples together. + If num_buckets == 1, all examples can be batched together. As the number of buckets grows only examples with similar + length can be grouped together. This trades-off speed with randomization. + Low number -> better randomization, High number -> faster training. + However if set too high the training speed will decrease. If num_buckets -> number of examples in the dataset the batch size + will be small impacting training speed and possibly performance. + NOTE: you have either to specify manually the bucket_boundaries or the number of buckets. + length_func : callable + Function used to get length of each example from the dataset. + This argument can be used only when the dataset is a Speechbrain DynamicItemDataset object. + Can be anything: e.g. lambda x: x["duration"]*16000 returns number of samples + if duration key in the annotation is in seconds and the file has 16kHz sampling freq. + shuffle : bool + Whether or not shuffle examples between each epoch. + batch_ordering : string + If ``random``, batches are randomly permuted; otherwise ``ascending`` or ``descending`` sorted by length. + max_batch_ex: int + If set, it limits the maximum number of examples that can be in a batch superseeding max_batch_length + in instances where the amount of examples will exceeed the value specified here. + E.g. you have a lot of short examples and the batch size for those will be too high, you can use this argument + to limit the batch size for these short examples. + bucket_boundaries : list + Overrides bucket_length_multiplier and left_bucket_length by specifying manually + the buckets right boundaries. + lengths_list: list + Overrides length_func by passing a list containing the length of each example + in the dataset. This argument must be set when the dataset is a plain + Pytorch Dataset object and not a DynamicItemDataset object as length_func + cannot be used on Pytorch Datasets. + epoch : int + The epoch to start at. + drop_last : bool + If ``True``, the sampler will drop the last examples which + have not been grouped. + verbose: bool + If ``True``, log also the stats for each batch at the first epoch. + """ + + def __init__( + self, + dataset, + args, + num_replicas = None, + rank = None, + shuffle = True, + seed = 0, + drop_last = False, + length_func=lambda x: x["duration"], + batch_ordering: str = "random", + max_batch_ex: int = None, + bucket_boundaries: List[int] = [], + lengths_list: List[int] = None, + epoch: int = 0, + verbose: bool = False, + ): + self.args = args + if num_replicas is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + num_replicas = dist.get_world_size() + if rank is None: + if not dist.is_available(): + raise RuntimeError("Requires distributed package to be available") + rank = dist.get_rank() + if rank >= num_replicas or rank < 0: + raise ValueError( + "Invalid rank {}, rank should be in the interval" + " [0, {}]".format(rank, num_replicas - 1)) + self.num_replicas = num_replicas + self.rank = rank + max_batch_length = self.args.max_num_tokens if dataset.split == "train" else self.args.val_max_num_tokens + logging.info(f"max_num_tokens per GPU for {dataset.split} split: {max_batch_length}") + num_buckets = self.args.num_buckets + ############# + + + + + self._dataset = dataset + self._ex_lengths = {} + # ex_ids = self._dataset.data_ids + self.verbose = verbose + + # We do not put a default on num_buckets to encourage users to play with this parameter + if num_buckets is None and len(bucket_boundaries) == 0: + raise RuntimeError( + "Please specify either num_buckets or bucket boundaries." + "Check the docs, and/or the tutorial !" + ) + assert lengths_list != None + max_len = int(self.args.audio_max_length * self.args.encodec_sr) + lengths_list = [min(l, max_len) for l in lengths_list] # replace all utt whose length is longer than max_len to max_len, will also do this in __getitem__ in dataset + for indx in range(len(lengths_list)): + self._ex_lengths[str(indx)] = lengths_list[indx] + # if lengths_list is not None: + # # take length of examples from this argument and bypass length_key + # for indx in range(len(lengths_list)): + # self._ex_lengths[str(indx)] = lengths_list[indx] + # else: + # # use length func + # if not isinstance(dataset, DynamicItemDataset): + # raise NotImplementedError( + # "Dataset should be a Speechbrain DynamicItemDataset when using length function" + # ) + # for indx in range(len(self._dataset)): + # self._ex_lengths[str(indx)] = length_func( + # self._dataset.data[ex_ids[indx]] + # ) + + if len(bucket_boundaries) > 0: + if not all([x >= 0 for x in bucket_boundaries]): + raise ValueError( + "All elements in bucket boundaries should be non-negative (>= 0)." + ) + if not len(set(bucket_boundaries)) == len(bucket_boundaries): + raise ValueError( + "Bucket_boundaries should not contain duplicates." + ) + np.testing.assert_array_equal( + np.array(bucket_boundaries), + np.array(sorted(bucket_boundaries)), + err_msg="The arg bucket_boundaries should be an ascending sorted list of non negative values values!", + ) + self._bucket_boundaries = np.array(sorted(bucket_boundaries)) + else: + # use num_buckets + self._bucket_boundaries = np.array( + self._get_boundaries_through_warping( + # max_batch_length=max_batch_length, + max_batch_length=max(lengths_list), + num_quantiles=num_buckets, + ) + ) + + self._max_batch_length = max_batch_length + self._shuffle_ex = shuffle + self._batch_ordering = batch_ordering + self._seed = seed + self._drop_last = drop_last + if max_batch_ex is None: + max_batch_ex = np.inf + self._max_batch_ex = max_batch_ex + # Calculate bucket lengths - how often does one bucket boundary fit into max_batch_length? + self._bucket_lens = [ + max(1, int(max_batch_length / self._bucket_boundaries[i])) + for i in range(len(self._bucket_boundaries)) + ] + [1] + self._epoch = epoch + self._cur_step = 0 + self.continue_flag = False + self._generate_batches() + self.num_samples = int(math.floor(len(self._batches) / self.num_replicas)) + self.total_size = int(self.num_samples * self.num_replicas) + self._replica_batches = self._batches[self.rank:self.total_size:self.num_replicas] + assert len(self._replica_batches) == self.num_samples, f"len(self._batches): {len(self._batches)}, self.total_size: {self.total_size}, self.num_samples: {self.num_samples},len(self._replica_batches): {len(self._replica_batches)}" + logging.info(f"len(self._batches): {len(self._batches)}") + logging.info(f"self.num_replicas: {self.num_replicas}") + logging.info(f"num of batches on each replica: {self.num_samples}") + + def get_durations(self, batch): + """Gets durations of the elements in the batch.""" + return [self._ex_lengths[str(idx)] for idx in batch] + + def _get_boundaries_through_warping( + self, max_batch_length: int, num_quantiles: int, + ) -> List[int]: + + # NOTE: the following lines do not cover that there is only one example in the dataset + # warp frames (duration) distribution of train data + logging.info("Batch quantisation in latent space") + # linspace set-up + num_boundaries = num_quantiles + 1 + # create latent linearly equal spaced buckets + latent_boundaries = np.linspace( + 1 / num_boundaries, num_quantiles / num_boundaries, num_quantiles, + ) + # get quantiles using lognormal distribution + quantiles = lognorm.ppf(latent_boundaries, 1) + # scale up to to max_batch_length + bucket_boundaries = quantiles * max_batch_length / quantiles[-1] + # compute resulting bucket length multipliers + length_multipliers = [ + bucket_boundaries[x + 1] / bucket_boundaries[x] + for x in range(num_quantiles - 1) + ] + # logging + logging.debug( + "Latent bucket boundary - buckets: {} - length multipliers: {}".format( + list(map("{:.2f}".format, bucket_boundaries)), + list(map("{:.2f}".format, length_multipliers)), + ) + ) + return list(sorted(bucket_boundaries)) + + def _permute_batches(self): + + if self._batch_ordering == "random": + # deterministically shuffle based on epoch and seed + g = torch.Generator() + g.manual_seed(self._seed + self._epoch) # since the random seed is based on self._seed and self._epoch, it should be the same for different processes when using DDP, and therefore the generated order should be the same across different process, this is important, because each replica will only take a portion of it, we want to make sure they take a non-overlapping portion, and all of them constitute the entire dataset + sampler = torch.randperm( + len(self._batches), generator=g + ).tolist() # type: ignore + tmp = [] + for idx in sampler: + tmp.append(self._batches[idx]) + self._batches = tmp + + elif self._batch_ordering == "ascending": + self._batches = sorted( + self._batches, + key=lambda x: max([self._ex_lengths[str(idx)] for idx in x]), + ) + elif self._batch_ordering == "descending": + self._batches = sorted( + self._batches, + key=lambda x: max([self._ex_lengths[str(idx)] for idx in x]), + reverse=True, + ) + else: + raise NotImplementedError + + def _generate_batches(self): + logging.info("DynamicBatchSampler: Generating dynamic batches") + if self._shuffle_ex: + # deterministically shuffle based on epoch and seed + g = torch.Generator() + g.manual_seed(self._seed + self._epoch) # since the random seed is based on self._seed and self._epoch, it should be the same for different processes when using DDP, and therefore the generated order should be the same across different process, this is important, because each replica will only take a portion of it, we want to make sure they take a non-overlapping portion, and all of them constitute the entire dataset + sampler = torch.randperm(len(self._dataset), generator=g).tolist() # type: ignore + # pyp note: this is actually randomly permoted indices + else: + # take examples as they are: e.g. they have been sorted + sampler = range(len(self._dataset)) # type: ignore + + self._batches = [] + bucket_batches = [[] for i in self._bucket_lens] + + stats_tracker = [ + {"min": np.inf, "max": -np.inf, "tot": 0, "n_ex": 0} + for i in self._bucket_lens + ] + + for idx in sampler: + # length of pre-sampled audio + item_len = self._ex_lengths[str(idx)] + # bucket to fill up most padding + bucket_id = np.searchsorted(self._bucket_boundaries, item_len) + # fill audio's duration into that bucket + bucket_batches[bucket_id].append(idx) + + stats_tracker[bucket_id]["min"] = min( + stats_tracker[bucket_id]["min"], item_len + ) + stats_tracker[bucket_id]["max"] = max( + stats_tracker[bucket_id]["max"], item_len + ) + stats_tracker[bucket_id]["tot"] += item_len + stats_tracker[bucket_id]["n_ex"] += 1 + # track #samples - why not duration/#frames; rounded up? + # keep track of durations, if necessary + + if ( + len(bucket_batches[bucket_id]) >= self._bucket_lens[bucket_id] + or len(bucket_batches[bucket_id]) >= self._max_batch_ex + ): + self._batches.append(bucket_batches[bucket_id]) + bucket_batches[bucket_id] = [] + # keep track of durations + + # Dump remaining batches + if not self._drop_last: + for batch in bucket_batches: + if batch: + self._batches.append(batch) + + self._permute_batches() # possibly reorder batches + + if self._epoch == 0: # only log at first epoch + # frames per batch & their padding remaining + boundaries = [0] + self._bucket_boundaries.tolist() + + for bucket_indx in range(len(self._bucket_boundaries)): + try: + num_batches = stats_tracker[bucket_indx]["tot"] // ( + self._max_batch_length + ) + pad_factor = ( + stats_tracker[bucket_indx]["max"] + - stats_tracker[bucket_indx]["min"] + ) / ( + stats_tracker[bucket_indx]["tot"] + / stats_tracker[bucket_indx]["n_ex"] + ) + except ZeroDivisionError: + num_batches = 0 + pad_factor = 0 + + logging.debug( + ( + "DynamicBatchSampler: Bucket {} with boundary {:.1f}-{:.1f} and " + + "batch_size {}: Num Examples {:.1f}, Num Full Batches {:.3f}, Pad Factor {:.3f}." + ).format( + bucket_indx, + boundaries[bucket_indx], + boundaries[bucket_indx + 1], + self._bucket_lens[bucket_indx], + stats_tracker[bucket_indx]["n_ex"], + num_batches, + pad_factor * 100, + ) + ) + + if self.verbose: + batch_stats = { + "tot_frames": [], + "tot_pad_frames": [], + "pad_%": [], + } + for batch in self._batches: + tot_frames = sum( + [self._ex_lengths[str(idx)] for idx in batch] + ) + batch_stats["tot_frames"].append(tot_frames) + max_frames = max( + [self._ex_lengths[str(idx)] for idx in batch] + ) + tot_pad = sum( + [ + max_frames - self._ex_lengths[str(idx)] + for idx in batch + ] + ) + batch_stats["tot_pad_frames"].append(tot_pad) + batch_stats["pad_%"].append(tot_pad / tot_frames * 100) + + padding_details = "Batch {} with {:.1f} frames with {} files - {:.1f} padding, {:.2f} (%) of total." + padding_details = "DynamicBatchSampler: " + padding_details + for i in range(len(self._batches)): + logging.debug( + padding_details.format( + i, + batch_stats["tot_frames"][i], + len(self._batches[i]), + batch_stats["tot_pad_frames"][i], + batch_stats["pad_%"][i], + ) + ) + + def __iter__(self): + + for batch in self._replica_batches: + yield batch + + + # if self._shuffle_ex: # re-generate examples if ex_ordering == "random" + # self._generate_batches() + # if self._batch_ordering == "random": + # # we randomly permute the batches only --> faster + # self._permute_batches() + + def set_epoch(self, epoch): + """ + You can also just access self.epoch, but we maintain this interface + to mirror torch.utils.data.distributed.DistributedSampler + """ + self._epoch = epoch + self._generate_batches() + self._replica_batches = self._batches[self.rank:self.total_size:self.num_replicas] + self.num_samples = int(math.floor(len(self._batches) / self.num_replicas)) + assert len(self._replica_batches) == self.num_samples, f"len(self._batches): {len(self._batches)}, self.total_size: {self.total_size}, self.num_samples: {self.num_samples},len(self._replica_batches): {len(self._replica_batches)}" + + if self.continue_flag: + self.continue_flag = False + self._replica_batches = self._replica_batches[self._cur_step:] + self.num_samples = len(self._replica_batches) + + + def __len__(self): + return self.num_samples + + def set_epoch_resume(self, epoch, cur_step): + self.continue_flag = True + self._epoch = epoch + self._cur_step = cur_step diff --git a/z_scripts/e830M.sh b/z_scripts/e830M.sh new file mode 100644 index 0000000..5394e83 --- /dev/null +++ b/z_scripts/e830M.sh @@ -0,0 +1,71 @@ +#!/bin/bash +source ~/miniconda3/etc/profile.d/conda.sh +conda activate voicecraft +export CUDA_VISIBLE_DEVICES=0,1,2,3 +export WORLD_SIZE=4 + +dataset=gigaspeech +mkdir -p ./logs/${dataset} + +exp_root="/data/scratch/pyp/exp_pyp/VoiceCraft" +exp_name=e830M +dataset_dir="/data/scratch/pyp/datasets/gigaspeech_phn_enc_manifest/xl" +encodec_codes_folder_name="encodec_16khz_4codebooks" + +# export CUDA_LAUNCH_BLOCKING=1 # for debugging + +torchrun --nnodes=1 --rdzv-backend=c10d --rdzv-endpoint=localhost:41977 --nproc_per_node=${WORLD_SIZE} \ +../main.py \ +--reduced_eog 1 \ +--drop_long 1 \ +--eos 2051 \ +--n_special 4 \ +--pad_x 0 \ +--codebook_weight "[5,1,0.5,0.1]" \ +--encodec_sr 50 \ +--num_steps 50000 \ +--lr 0.05 \ +--warmup_fraction 0.01 \ +--optimizer_name "ScaledAdam" \ +--pseudo_epoch_size 3000 \ +--reduce_lr_start_step 3000 \ +--reduce_lr_start_epoch 4 \ +--clipping_update_period 1000 \ +--d_model 2048 \ +--audio_embedding_dim 2048 \ +--nhead 16 \ +--num_decoder_layers 16 \ +--max_num_tokens 100000 \ +--gradient_accumulation_steps 26 \ +--val_max_num_tokens 6000 \ +--num_buckets 6 \ +--audio_max_length 20 \ +--audio_min_length 2 \ +--text_max_length 400 \ +--text_min_length 10 \ +--mask_len_min 1 \ +--mask_len_max 600 \ +--tb_write_every_n_steps 10 \ +--print_every_n_steps 400 \ +--val_every_n_steps 1600 \ +--text_vocab_size 100 \ +--text_pad_token 100 \ +--phn_folder_name "phonemes" \ +--manifest_name "manifest_large16khz_lessambi" \ +--encodec_folder_name ${encodec_codes_folder_name} \ +--audio_vocab_size 2048 \ +--empty_token 2048 \ +--eog 2049 \ +--audio_pad_token 2050 \ +--n_codebooks 4 \ +--max_n_spans 3 \ +--shuffle_mask_embedding 0 \ +--mask_sample_dist poisson1 \ +--max_mask_portion 0.9 \ +--min_gap 5 \ +--num_workers 8 \ +--dynamic_batching 1 \ +--dataset $dataset \ +--exp_dir "${exp_root}/${dataset}/${exp_name}" \ +--dataset_dir ${dataset_dir} +# >> ./logs/${dataset}/${exp_name}.log 2>&1 \ No newline at end of file