From 6d1397ed4bb65933d02725623c122a157544a729 Mon Sep 17 00:00:00 2001 From: Michal Guzek Date: Fri, 9 Feb 2024 13:45:52 -0800 Subject: [PATCH] TensorRT 9.3 updates (#3661) * TensorRT 9.3 updates (no submodule updates) Signed-off-by: Michal Guzek * Update to ONNX-TensorRT 9.3 Signed-off-by: Michal Guzek --------- Signed-off-by: Michal Guzek Co-authored-by: Michal Guzek --- CHANGELOG.md | 11 +- README.md | 10 +- VERSION | 2 +- demo/Diffusion/README.md | 18 +- demo/Diffusion/calibration-prompts.txt | 1079 +++++++++++++++++ demo/Diffusion/calibration.py | 177 +++ demo/Diffusion/models.py | 108 +- demo/Diffusion/requirements.txt | 7 +- demo/Diffusion/stable_diffusion_pipeline.py | 115 +- demo/Diffusion/utilities.py | 157 ++- .../HuggingFace-Diffusers/README.md | 2 +- parsers/onnx | 2 +- tools/Polygraphy/CHANGELOG.md | 61 + tools/Polygraphy/docs/index.rst | 6 +- .../07_inspecting_tactic_replays/README.md | 4 +- .../README.md | 15 +- .../README.md | 31 + .../nested_local_function.onnx | Bin 0 -> 807 bytes .../01_match_and_replace_plugin/README.md | 122 ++ ...aph_with_subgraph_matching_toy_plugin.onnx | Bin 0 -> 240 bytes .../plugins/toyPlugin/__init__.py | 0 .../plugins/toyPlugin/pattern.py | 31 + .../cli/run/01_comparing_frameworks/README.md | 6 +- tools/Polygraphy/polygraphy/README.md | 4 +- tools/Polygraphy/polygraphy/__init__.py | 2 +- .../polygraphy/backend/onnx/loader.py | 3 +- .../polygraphy/backend/onnxrt/runner.py | 6 +- .../polygraphy/backend/tf/__init__.py | 2 +- .../backend/trt/algorithm_selector.py | 28 +- .../polygraphy/backend/trt/config.py | 87 +- .../polygraphy/backend/trt/loader.py | 34 +- .../polygraphy/backend/trt/runner.py | 56 +- .../Polygraphy/polygraphy/backend/trt/util.py | 219 +++- tools/Polygraphy/polygraphy/common/struct.py | 6 +- .../polygraphy/comparator/compare.py | 223 +++- .../polygraphy/comparator/data_loader.py | 200 ++- .../polygraphy/datatype/datatype.py | 16 +- tools/Polygraphy/polygraphy/datatype/numpy.py | 2 +- tools/Polygraphy/polygraphy/datatype/onnx.py | 2 +- .../polygraphy/datatype/tensorrt.py | 9 +- tools/Polygraphy/polygraphy/datatype/torch.py | 2 +- tools/Polygraphy/polygraphy/json/serde.py | 8 +- tools/Polygraphy/polygraphy/mod/importer.py | 110 +- .../tools/args/backend/trt/config.py | 41 +- .../tools/args/backend/trt/loader.py | 17 +- .../tools/args/backend/trt/runner.py | 27 +- .../tools/args/comparator/data_loader.py | 13 + .../polygraphy/tools/check/subtool/lint.py | 40 +- .../tools/inspect/subtool/capability.py | 99 +- .../polygraphy/tools/plugin/README.md | 35 + .../polygraphy/tools/plugin/__init__.py | 1 + .../polygraphy/tools/plugin/plugin.py | 34 + .../tools/plugin/subtool/__init__.py | 4 + .../tools/plugin/subtool/list_plugins.py | 36 + .../polygraphy/tools/plugin/subtool/match.py | 43 + .../tools/plugin/subtool/plugin_base.py | 131 ++ .../tools/plugin/subtool/replace.py | 116 ++ tools/Polygraphy/polygraphy/tools/registry.py | 1 + tools/Polygraphy/polygraphy/tools/sparse.py | 22 +- .../polygraphy/tools/surgeon/README.md | 3 + .../tools/surgeon/subtool/__init__.py | 2 + .../surgeon/subtool/weight_reconstruct.py | 88 ++ .../tools/surgeon/subtool/weight_strip.py | 419 +++++++ .../polygraphy/tools/surgeon/surgeon.py | 4 +- tools/Polygraphy/polygraphy/util/array.py | 79 +- .../backend/trt/test_algorithm_selector.py | 173 +-- .../tests/backend/trt/test_config.py | 106 +- .../tests/backend/trt/test_loader.py | 98 +- .../tests/backend/trt/test_runner.py | 57 + .../Polygraphy/tests/backend/trt/test_util.py | 118 +- .../Polygraphy/tests/common/test_datatype.py | 21 +- .../tests/comparator/test_compare.py | 176 ++- .../tests/comparator/test_data_loader.py | 55 +- tools/Polygraphy/tests/helper.py | 2 +- .../Polygraphy/tests/mod/test_dependencies.py | 1 + tools/Polygraphy/tests/mod/test_importer.py | 37 +- tools/Polygraphy/tests/models/conv.onnx | Bin 4259 -> 4259 bytes ...aph_with_subgraph_matching_toy_plugin.onnx | Bin 0 -> 240 bytes tools/Polygraphy/tests/models/make_models.py | 141 ++- .../tests/models/matmul.exclude_list.txt | 1 + .../Polygraphy/tests/models/matmul.fp16.onnx | Bin 392 -> 392 bytes tools/Polygraphy/tests/models/matmul.onnx | Bin 648 -> 648 bytes tools/Polygraphy/tests/models/meta.py | 49 +- .../models/plugins/toyPlugin/__init__.py | 0 .../tests/models/plugins/toyPlugin/pattern.py | 31 + .../tests/models/qdq_conv.exclude_list.txt | 1 + tools/Polygraphy/tests/models/qdq_conv.onnx | Bin 0 -> 5462 bytes .../tests/models/residual_block.onnx | Bin 0 -> 347016 bytes .../tests/models/sparse.conv.exclude_list.txt | 1 + .../Polygraphy/tests/models/sparse.conv.onnx | Bin 0 -> 4283 bytes .../tests/models/sparse.matmul.onnx | Bin 0 -> 672 bytes .../tests/models/transpose_matmul.onnx | Bin 0 -> 706 bytes .../tests/models/weightless.conv.onnx | Bin 0 -> 178 bytes .../tests/models/weightless.matmul.bf16.onnx | Bin 0 -> 151 bytes .../tests/models/weightless.matmul.fp16.onnx | Bin 0 -> 151 bytes .../tests/models/weightless.qdq_conv.onnx | Bin 0 -> 488 bytes .../tests/models/weightless.sparse.conv.onnx | Bin 0 -> 212 bytes .../models/weightless.sparse.matmul.onnx | Bin 0 -> 185 bytes .../models/weightless.transpose_matmul.onnx | Bin 0 -> 179 bytes tools/Polygraphy/tests/requirements.txt | 3 +- tools/Polygraphy/tests/test_examples.py | 158 ++- .../tools/args/backend/trt/test_config.py | 286 ++++- .../tools/args/backend/trt/test_loader.py | 63 +- .../tools/args/comparator/test_data_loader.py | 2 + tools/Polygraphy/tests/tools/conftest.py | 35 +- tools/Polygraphy/tests/tools/test_check.py | 2 +- tools/Polygraphy/tests/tools/test_inspect.py | 41 +- tools/Polygraphy/tests/tools/test_plugin.py | 108 ++ tools/Polygraphy/tests/tools/test_run.py | 36 + tools/Polygraphy/tests/tools/test_surgeon.py | 109 +- tools/Polygraphy/tests/util/test_serde.py | 22 +- 111 files changed, 5491 insertions(+), 680 deletions(-) create mode 100644 demo/Diffusion/calibration-prompts.txt create mode 100644 demo/Diffusion/calibration.py create mode 100644 tools/Polygraphy/examples/cli/inspect/09_inspecting_tensorrt_static_onnx_support/README.md create mode 100644 tools/Polygraphy/examples/cli/inspect/09_inspecting_tensorrt_static_onnx_support/nested_local_function.onnx create mode 100644 tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/README.md create mode 100644 tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/graph_with_subgraph_matching_toy_plugin.onnx create mode 100644 tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/__init__.py create mode 100644 tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/pattern.py create mode 100644 tools/Polygraphy/polygraphy/tools/plugin/README.md create mode 100644 tools/Polygraphy/polygraphy/tools/plugin/__init__.py create mode 100644 tools/Polygraphy/polygraphy/tools/plugin/plugin.py create mode 100644 tools/Polygraphy/polygraphy/tools/plugin/subtool/__init__.py create mode 100644 tools/Polygraphy/polygraphy/tools/plugin/subtool/list_plugins.py create mode 100644 tools/Polygraphy/polygraphy/tools/plugin/subtool/match.py create mode 100644 tools/Polygraphy/polygraphy/tools/plugin/subtool/plugin_base.py create mode 100755 tools/Polygraphy/polygraphy/tools/plugin/subtool/replace.py create mode 100644 tools/Polygraphy/polygraphy/tools/surgeon/subtool/weight_reconstruct.py create mode 100644 tools/Polygraphy/polygraphy/tools/surgeon/subtool/weight_strip.py create mode 100644 tools/Polygraphy/tests/models/graph_with_subgraph_matching_toy_plugin.onnx create mode 100644 tools/Polygraphy/tests/models/matmul.exclude_list.txt create mode 100644 tools/Polygraphy/tests/models/plugins/toyPlugin/__init__.py create mode 100644 tools/Polygraphy/tests/models/plugins/toyPlugin/pattern.py create mode 100644 tools/Polygraphy/tests/models/qdq_conv.exclude_list.txt create mode 100644 tools/Polygraphy/tests/models/qdq_conv.onnx create mode 100644 tools/Polygraphy/tests/models/residual_block.onnx create mode 100644 tools/Polygraphy/tests/models/sparse.conv.exclude_list.txt create mode 100644 tools/Polygraphy/tests/models/sparse.conv.onnx create mode 100644 tools/Polygraphy/tests/models/sparse.matmul.onnx create mode 100644 tools/Polygraphy/tests/models/transpose_matmul.onnx create mode 100644 tools/Polygraphy/tests/models/weightless.conv.onnx create mode 100644 tools/Polygraphy/tests/models/weightless.matmul.bf16.onnx create mode 100644 tools/Polygraphy/tests/models/weightless.matmul.fp16.onnx create mode 100644 tools/Polygraphy/tests/models/weightless.qdq_conv.onnx create mode 100644 tools/Polygraphy/tests/models/weightless.sparse.conv.onnx create mode 100644 tools/Polygraphy/tests/models/weightless.sparse.matmul.onnx create mode 100644 tools/Polygraphy/tests/models/weightless.transpose_matmul.onnx create mode 100644 tools/Polygraphy/tests/tools/test_plugin.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f42a25b..8010b07b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # TensorRT OSS Release Changelog -## 9.2.0 GA - 2023-12-04 +## 9.3.0 GA - 2024-02-09 + +Key Features and Updates: + + - Demo changes + - Faster Text-to-image using SDXL & INT8 quantization using AMMO + - Updated tooling + - Polygraphy v0.49.7 + +## 9.2.0 GA - 2023-11-27 Key Features and Updates: diff --git a/README.md b/README.md index 55558319..374e7df0 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ You can skip the **Build** section to enjoy TensorRT with Python. To build the TensorRT-OSS components, you will first need the following software packages. **TensorRT GA build** -* TensorRT v9.2.0.5 +* TensorRT v9.3.0.1 * Available from direct download links listed below **System Packages** @@ -73,16 +73,16 @@ To build the TensorRT-OSS components, you will first need the following software If using the TensorRT OSS build container, TensorRT libraries are preinstalled under `/usr/lib/x86_64-linux-gnu` and you may skip this step. Else download and extract the TensorRT GA build from [NVIDIA Developer Zone](https://developer.nvidia.com) with the direct links below: - - [TensorRT 9.2.0.5 for CUDA 11.8, Linux x86_64](https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/9.2.0/tensorrt-9.2.0.5.linux.x86_64-gnu.cuda-11.8.tar.gz) - - [TensorRT 9.2.0.5 for CUDA 12.2, Linux x86_64](https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/9.2.0/tensorrt-9.2.0.5.linux.x86_64-gnu.cuda-12.2.tar.gz) + - [TensorRT 9.3.0.1 for CUDA 11.8, Linux x86_64](https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/9.3.0/tensorrt-9.3.0.1.linux.x86_64-gnu.cuda-11.8.tar.gz) + - [TensorRT 9.3.0.1 for CUDA 12.2, Linux x86_64](https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/9.3.0/tensorrt-9.3.0.1.linux.x86_64-gnu.cuda-12.2.tar.gz) **Example: Ubuntu 20.04 on x86-64 with cuda-12.2** ```bash cd ~/Downloads - tar -xvzf tensorrt-9.2.0.5.linux.x86_64-gnu.cuda-12.2.tar.gz - export TRT_LIBPATH=`pwd`/TensorRT-9.2.0.5 + tar -xvzf tensorrt-9.3.0.1.linux.x86_64-gnu.cuda-12.2.tar.gz + export TRT_LIBPATH=`pwd`/TensorRT-9.3.0.1 ``` ## Setting Up The Build Environment diff --git a/VERSION b/VERSION index 638f74da..68edb8da 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.2.0.5 +9.3.0.1 diff --git a/demo/Diffusion/README.md b/demo/Diffusion/README.md index e5c0de10..8786236e 100755 --- a/demo/Diffusion/README.md +++ b/demo/Diffusion/README.md @@ -7,7 +7,7 @@ This demo application ("demoDiffusion") showcases the acceleration of Stable Dif ### Clone the TensorRT OSS repository ```bash -git clone git@github.com:NVIDIA/TensorRT.git -b release/9.2 --single-branch +git clone git@github.com:NVIDIA/TensorRT.git -b release/9.3 --single-branch cd TensorRT ``` @@ -16,7 +16,7 @@ cd TensorRT Install nvidia-docker using [these intructions](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker). ```bash -docker run --rm -it --gpus all -v $PWD:/workspace nvcr.io/nvidia/pytorch:23.07-py3 /bin/bash +docker run --rm -it --gpus all -v $PWD:/workspace nvcr.io/nvidia/pytorch:23.12-py3 /bin/bash ``` ### Install latest TensorRT release @@ -26,7 +26,7 @@ python3 -m pip install --upgrade pip python3 -m pip install --pre --upgrade --extra-index-url https://pypi.nvidia.com tensorrt ``` -> NOTE: TensorRT 9.0 is only available as a pre-release +> NOTE: TensorRT 9.x is only available as a pre-release Check your installed version using: `python3 -c 'import tensorrt;print(tensorrt.__version__)'` @@ -48,8 +48,8 @@ diffusers 0.23.1 onnx 1.14.0 onnx-graphsurgeon 0.3.26 onnxruntime 1.15.1 -polygraphy 0.49.1 -tensorrt 9.2.0.5 +polygraphy 0.49.7 +tensorrt 9.3.0.1 tokenizers 0.13.2 torch 2.1.0 transformers 4.31.0 @@ -137,6 +137,14 @@ It is also possible to combine multiple LoRAs. python3 demo_txt2img_xl.py "Picture of a rustic Italian village with Olive trees and mountains" --version=xl-1.0 --lora-path "ostris/crayon_style_lora_sdxl" "ostris/watercolor_style_lora_sdxl" --lora-scale 0.3 0.7 --onnx-dir onnx-sdxl-lora --engine-dir engine-sdxl-lora --build-enable-refit ``` +### Faster Text-to-image using SDXL & INT8 quantization using AMMO + +```bash +python3 demo_txt2img_xl.py "a photo of an astronaut riding a horse on mars" --version xl-1.0 --onnx-dir onnx-sdxl --engine-dir engine-sdxl --int8 --quantization-level 3 +``` + +Note that the calibration process can be quite time-consuming, and will be repeated if `--quantization-level`, `--denoising-steps`, or `--onnx-dir` is changed. + ### Faster Text-to-Image using SDXL + LCM (Latent Consistency Model) LoRA weights [LCM-LoRA](https://arxiv.org/abs/2311.05556) produces good quality images in 4 to 8 denoising steps instead of 30+ needed base model. Note that we use LCM scheduler and disable classifier-free-guidance by setting `--guidance-scale` to 0. LoRA weights are fused into the ONNX and finalized TensorRT plan files in this example. diff --git a/demo/Diffusion/calibration-prompts.txt b/demo/Diffusion/calibration-prompts.txt new file mode 100644 index 00000000..8b224e6d --- /dev/null +++ b/demo/Diffusion/calibration-prompts.txt @@ -0,0 +1,1079 @@ +Portrait shot of a woman, yellow shirt, photograph +Little girl holding a teddy bear, in the middle of nowhere, photograph +Portrait of an arctic fox in the tundra, light teal and amber, minimalist, photograph +Confused woman, sci - fi, future, blue glow color, orange, hologram, photograph +Symmetrical, macro shot, crying womans face, half of face is organic flowing RGB low poly, depth of field +Beautiful woman future funk psychedelic +Mosaic of a colorful mushroom with intricate patterns, vibrant and detailed, sharp, mosaic background, vector art +Illustration of a man in red hoodie, minimalist, graphic design poster art, dark cyan and sky - blue, honeycore +a bottle of perfume on a clean backdrop, surrounded by fragrant white flowers, product photography, minimalistic, natural light +a bedroom with large windows and modern furniture, gray and gold, luxurious, mid century modern style +an aerial drone shot of the breathtaking landscape of the Bora Bora islands, with sparkling waters under the sun +extreme closeup shot of an old man with a long gray hair and head covered in wrinkles; focused expression looking at camera +Simple flat vector illustration of a woman sitting at the desk with her laptop with a puppy, isolated on white background +Chibi pixel art, game asset for an rpg game on a white background featuring the armor of a dragon sorcerer wielding the power of fire surrounded by a matching item set +a macro wildlife photo of a green frog in a rainforest pond, highly detailed, eye-level shot +kid's coloring book, a happy young girl holding a flower, cartoon, thick lines, black and white, white background +Golden-haired elementary school white boy hugging his black-hair Taiwanese buddy face-to-face on dusk street, unreal engine, greg rutkowski, loish, rhads, beeple, makoto shinkai and lois van baarle, ilya kuvshinov, rossdraws, tom bagshaw, alphonse mucha, global illumination, detailed and intricate environment +Tan skin Anime boy wearing a large black sweater and cat ear beanie with brown hair and eyes, full body, baggy cargo pants, full body, reference +Fawn French Bulldog with big eyes, short legs, and chunky, stocky body eating food +A white goose holding a paint brush +Black, African descent, looks Japanese, wears glasses, Naruto type art, bandage on his nose, male, Anime 2D art, lazy eyes, Japanese earring in one ear, no beard, smiles sinisterly +Male cow fursona wearing a red beanie +a beautiful hyper-realistic anime Lofi, painted by greg rutkowski makoto shinkai takashi takeuchi studio ghibli, akihiko yoshida, anime, clean soft lighting, finely detailed features, high-resolution, perfect art, stunning atmosphere, trending on pixiv fanbox +a woman with a beautiful face is enjoying a summer festival wearing a kimono, long white hair, looks like an older sister with a small body, is holding a traditional Japanese umbrella with a faint smile, her head is facing backwards as if inviting her to play and she is running with her arms behind her, there is also a lock of patterned hair flower +A stunning photograph of a serene mountain lake at sunrise, with crystal-clear reflections and soft pastel skies +A high-resolution image of an ancient oak tree in a lush forest, sunlight filtering through the leaves +An ultra-realistic photograph of the Milky Way galaxy seen from a remote desert, under clear skies +A detailed image of a colorful street market in Marrakech at golden hour, with vibrant fabrics and bustling crowds +A professional photograph of a majestic bald eagle in flight, with a crisp focus on its sharp eyes and detailed feathers +A perfect image of a charming cobblestone street in Prague, with historical buildings and a peaceful early morning atmosphere +A photo-realistic image of a modern city skyline at night, with shimmering lights and reflections on a river +An authentic-looking photograph of the Northern Lights over a snowy Lapland landscape, with vivid colors and clear stars +A high-quality image of a vintage 1950s diner, with classic cars parked outside and a sunset backdrop +An elegant photograph of a grand ballroom from the Victorian era, with ornate decorations and a grand chandelier +A striking photograph of a powerful thunderstorm over the ocean, with dramatic lightning strikes and rolling waves +An image of a peaceful Zen garden with smooth stones, raked sand, and a calming waterfall +A high-resolution photograph of a seasoned fisherman at dawn, casting a net into the sea, with the golden light reflecting off the water +A professional close-up shot of a woman's face, half-illuminated by the sunset, showcasing a detailed texture of her skin and a contemplative expression +An image capturing a street dancer in mid-air during a dynamic breakdance move, with urban graffiti in the background +A vibrant photograph of a group of people dressed in traditional attire at a cultural festival, dancing in a blur of colors and fabrics +A cinematic-style photograph of a lone astronaut in a spacesuit, standing on a rocky alien landscape with Earth visible in the sky above +Capture the quiet intensity in the eyes of a chess grandmaster poised over the board in a high-stakes match +Close-up: A young girl's freckled face, focused and thoughtful, as she reads a book under the shade of an old tree +Underwater photography of a diver among swirling schools of fish, light filtering down from above +Evening falls on a city street musician, his guitar casting long shadows as he strums for the passing crowd +High above the city, a construction worker perches on a steel beam, with a backdrop of the skyline stretching into the distance +Document the intense expression of a potter as they shape a clay vessel, hands and wheel both a blur of motion +A street portrait captures the weathered face of a long-time vendor, his cart a staple in the neighborhood for generations +During golden hour, a group of children race through a field, their silhouettes a dance of joy against the setting sun +Zoomed-in shot capturing the intense focus of a violinist as the bow gracefully sweeps across the strings, emotions etched into their performance +Evening light bathes a street artist in a halo as they spray paint a vibrant mural, the colors telling a story as much as the subject's concentrated gaze +A mid-action image of a chef's hands chopping herbs, with fine details showing flying droplets of water from the fresh greens +On a misty morning, capture the solitary figure of a jogger on a deserted trail, their breath and stride in sync +High in the mountains, a hiker reaches the summit, standing triumphantly with a panoramic view stretching behind them +Illuminated by the soft glow of a desk lamp, a writer pauses, pen in hand, surrounded by stacks of manuscripts, lost in thought +eerie, corruption, beautiful, young woman, sad eyes, tears running down, crying, innocence, light, vaporwave aesthetic, synthwave, colorful, psychedelic, crown, long gown, flowers, bees, butterflies, ribbons, ornate, intricate, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +wolf merged with crow,! photorealistic,! concept art +To every living being, and every living soul. Now cometh the age of the stars. A thousand year voyage under the wisdom of the Moon. Here begins the chill night that encompasses all, reaching the great beyond. Into fear, doubt, and loneliness... As the path stretches into darkness. Mysterious shadow, detailed, digital, trending on artstation, hyper realistic, dark colours, 4k, dark aesthetic, in the style of James C. Christensen +A cowboy cat with big and cute eyes, fine-face, realistic shaded perfect face, fine details. realistic shaded lighting poster by Ilya Kuvshinov katsuhiro otomo ghost-in-the-shell, magali villeneuve, artgerm, Jeremy Lipkin and Michael Garmash, Rob Rey and Kentarõ Miura style, trending on art station +a very beautiful anime cute girl, full body, long wavy blond hair, sky blue eyes, full round face, short smile, fancy top, miniskirt, front view, summer lake setting, cinematic lightning, medium shot, mid-shot, highly detailed, trending on Artstation, Unreal Engine 4k, cinematic wallpaper by Stanley Artgerm Lau, WLOP, Rossdraws, James Jean, Andrei Riabovitchev, Marc Simonetti +close-up portrait of the perfect and symmetrical face of a beautiful Cotton Mill Girl, symmetrical, centered, dramatic angle, ornate, details, smooth, sharp focus, illustration, realistic, cinematic, artstation, award winning, rgb , unreal engine, octane render, cinematic light, macro, depth of field, blur, red light and clouds from the back, highly detailed epic cinematic concept art CG render made in Maya, Blender and Photoshop, octane render, excellent composition, dynamic dramatic cinematic lighting, aesthetic, very inspirational, arthouse by Henri Cartier Bresson +highly detailed portrait of beautiful ethereal woman in ornate clothing, stephen bliss, unreal engine, fantasy art by greg rutkowski, loish, rhads, ferdinand knab, makoto shinkai and lois van baarle, ilya kuvshinov, rossdraws, tom bagshaw, global illumination, radiant light, detailed and intricate environment +Close-up portrait of young asian girl, long blonde hair, dark fantasy, portrait, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +necromancer glowing with purple magic, red hair, female, glacier landscape, D&D, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, art by Artgerm and Greg Rutkowski and Alphonse Mucha +a portrait of riddler, fantasy, sharp focus, intricate, elegant, digital painting, artstation, matte, highly detailed, concept art, illustration, ambient lighting, art by ilya kuvshinov, artgerm, alphonse mucha, and greg rutkowski +duotone dark scifi illustration 3 / 4 portrait of dream as if you live forever live as if you die tomorrow. cinematic lighting mad scientist style. golden ratio accidental renaissance. in the style of jean michel basquiat, beksisnski, and pablo picasso. graffiti art, scifi, fantasy, hyper detailed. octane render. concept art. trending on artstation +elon musk as neo from the matrix, realistic portrait, symmetrical, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +patrick star with a sad!!! expression slouching on a bench in the bikini bottom, global illumination!!! dim lighting, midnight, cinematic, extremely detailed, beautiful, stunning composition, beautiful light rays, trending on artstation +a girl in a hat with a bouquet of peonies looks out the window at a blooming garden, vivid color, highly detailed, cyberpunk, digital painting, artstation, concept art, matte, sharp focus, art by vrubel +a detailed concept art of a fantasy jingle bell infused with magic, trending on artstation, digital art, 4 k, intricate, octane render, sharp focus +“ dungeons and dragons tabaxi rogue, anthromorphic cat person with a repeating crossbow in a medieval city, small and big, illustration, fantasy, trending on artstation ” +fantasy, book cover, concept art, by greg rutkowski and craig mullins, cozy atmospheric +Amelie Poulain painted by Raphael volumetric lighting, back lighting, rimlight, dramatic lighting, digital painting, highly detailed, artstation, sharp focus, illustration, Artgerm, Jean-L�on G�r�me , ruan jia +soft bokeh front shot photo of a mclaren steampunk concept car, cinematic, fine details, symmetrical, 4 k, digital art, wallpaper +dior runway show, light, shadows, reflections, golden, gold, epic composition, intricate, elegant, volumetric lighting, digital painting, highly detailed, artstation, sharp focus, illustration, concept art, ruan jia, steve mccurry +elven princess assassin, beautiful shadowing, 3 d shadowing, reflective surfaces, illustrated completely, 8 k beautifully detailed pencil illustration, extremely hyper - detailed pencil illustration, intricate, epic composition, very very kawaii, masterpiece, bold complimentary colors. stunning masterfully illustrated by artgerm and range murata. +gorgeous red fox in a suit drinking champagne, digital art, landscape, fantasy art, octane render, ureal engine, high detail, very realistic, by greg rutkowski. by james gurney +an extremely psychedelic portrait of medusa as willy wonka, surreal, lsd, face, detailed, intricate, elegant, lithe, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration +portrait painting of a muscular bloodied mixed girl, ultra realistic, cyberpunk hacknaut, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and alphonse mucha +concept art for the main character in the award winning film named life is better in pink. the character is a unnaturally beautiful teenage girl with deep dark blue eyes and long curled pink hair, wearing light pink clothes. realistic cg render, anatomically correct, high key lighting, trending on art station, vibrant colors. cute and highly detailed eyes. +beautiful woman, illustration, painting oil on canvas, intricate portrait, detailed, illustration, hd, digital art, overdetailed, art, concept, art +detailed full body concept art illustration oil painting of an anthropomorphic capybara cook in full intricate clothing, biomutant, ultra detailed, digital art, octane render +of a calm ocean with large strange cute happy flying creatures with huge eyes, mouth, long tongue and round teeth appearing from the sky, in the style of gehry and gaudi, macro lens, highly detailed, shallow depth of fielf, digital painting, trending artstation, concept art, illustration, cinematic lighting, vibrant colors, photorealism, epic, octane render +symmetry, samurai, lines, brown skin, machine face, intricate, elegant, highly detailed, digital painting, artstation, cgsociety, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, 8 k +cyberpunk Normani as aeon flux profile picture by Greg Rutkowski, dynamic pose, intricate, futuristic, fantasy, elegant, by Stanley Artgerm Lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman Rockwell, metal chrome, shiny, rainy background, asymmetric, afro hair, +chris tucker as dhalsim street fighter, jump kick, 4 k, ultra realistic, detailed focused art by artgerm and greg rutkowski and alphonse mucha +epic scene where mystical dead monk sitting in front of an epic portal, epic angle and pose, symmetrical artwork, 3d with depth of field, blurred background, cybernetic orchid flower butterfly jellyfish crystal dragon, female face skull phoenix bird, translucent, nautilus, energy flow. a highly detailed epic cinematic concept art CG render. made in Maya, Blender and Photoshop, octane render, excellent composition, cinematic dystopian brutalist atmosphere, dynamic dramatic cinematic lighting, aesthetic, very inspirational, arthouse. y Greg Rutkowski, Ilya Kuvshinov, WLOP, Stanley Artgerm Lau, Ruan Jia and Fenghua Zhong +concept art of futuristic modular military base, top angle, oil painting by jama jurabaev, extremely detailed, brush hard, artstation, for aaa game, high quality, brush stroke +portrait of natalie wood eating hamburgers, extra onions and ketchup, luscious patty with sesame seeds, feminine ethereal, handsome, d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +Trending on Artstation, Dark and rainy mega city with towering walls built to block the migrants of the coming climate change migrant crisis showing piles of hundred bodies outside to maintain a quality of life for those who can survive the severe and deadly weather patterns observing small children targeted by advanced military style drones, dystopian, concept art illustration, tilt shift background, wide depth of field, 8k, 35mm film grain +hard surface form fused with organic form fashion outfit design, rainbow iridescent accents, full body frontal view, Peter mohrbacher, zaha hadid, tsutomu nihei, emil melmoth, zdzislaw belsinki, Craig Mullins, yoji shinkawa, trending on artstation, beautifully lit, hyper detailed, insane details, intricate, elite, ornate, elegant, luxury, dramatic lighting, CGsociety, hypermaximalist, golden ratio, octane render, weta digital, micro details, ray trace, 8k, +Gary Busey portrait by Stanley Artgerm Lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman Rockwell +( cyberpunk 2 0 7 7, bladerunner 2 0 4 9 ), a complex thick bifurcated robotic cnc surgical arm cybernetic symbiosis hybrid mri 3 d printer machine making a bio chemical lab, art by artgerm and greg rutkowski and alphonse mucha, biomechanical, lens orbs, global illumination, lounge, architectural, f 3 2, +a vampire, male, mid - 3 0 s aged, long black hair, clean shaven, in red and black, high fantasy, realistic, highly detailed, concept art, 8 k. +a elderly wizard casting a black fireball | | pencil sketch, realistic shaded, fine details, realistic shaded lighting poster by greg rutkowski, magali villeneuve, artgerm, jeremy lipkin and michael garmash and rob rey +An elegant green, blue dragon, sitting on a clearing in a flowery jungle, detailed, mtg, digital illustration, trending on artstation +a landscape made of whimsical energy and fibrous magic, artstation landscape, artstation digital, illustrated by eddie mendoza and greg rutkowski, trending on artstation, cgsociety contest winner, cgsociety hd, cgsociety 4 k uhd, 4 k, 8 k +a cosmic painting of prince in space. mindblowing colours, trending on artstation. highly detailed face. +martian chronicles, by jean delville and sophie anderson and mandy jurgens, retrofuturism, moody atmosphere, cinematic atmospheric, cinematic lighting, golden ratio, perfect composition, elegant, no crop, extremely detailed, 4 k, hd, sharp focus, masterpiece, trending on artstation +a highly detailed metahuman 4 k close up render of a seraphim bella hadid monument renaissance in iris van herpen dress schiaparelli in diamonds crystals swarovski and jewelry iridescent in style of alphonse mucha gustav klimt trending on artstation made in unreal engine 4 +fever of the night, a grime tale of the night fever, disco club of the occult, digital painting, artstation, ristan eaton, victo ngai, artgerm, rhads, ross draws, anime styled +symmetrical, full body portrait of a woman with short wavy hair, round face, cottagecore!!, lake, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +fantasy man sitting in library, gold brocaded dark blue clothes, short black hair, books, reddish brown engraved shelves, sharp focus, intricate, extremely detailed, cinematic lighting, smooth, ultra realistic illustration, high fantasy, elegant, artgerm, greg rutkowski, alphonse mucha magali villeneuve +an anthropomorphic deer, fursona!!! by don bluth, by kawacy, trending on artstation, full body +a cartoon squirrel drawn in concept art style +russian poet alexander pushkin and shrek having breakfast together, portrait, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +beautiful woman on a turquise vespa moped, in the style of artgerm, gerald brom, atey ghailan and mike mignola, vibrant colors and hard shadows and strong rim light, plain background, comic cover art, trending on artstation, masterpiece +a martian landscape, by ralph mac quarrie and francois schuiten and albert bierstadt and ernst haeckel and james jean and john singer sargent, cinematic lighting, moody atmosphere, golden ratio, perfect composition, elegant and stylish look, artstation, concept art, high quality +� anime, full body, a pretty girl taking the college entrance exam, highly intricate detailed, light and shadow effects, intricate, highly detailed, digital painting, art station, concept art, smooth, sharp focus, illustration, advanced digital anime art, art by artgerm and greg rutkowski and alphonse mucha and william - adolphe bouguereau, craig mullins, j. c. leyendecker, atmospheric lighting, detailed face, by makoto shinkai, stanley artgerm lau, wlop, rossdraws � +the second coming of the buddah, by dan mumford and ross tran, cosmic, heavenly, god rays, intricate detail, cinematic, 8 k, cel shaded, unreal engine, featured on artstation, pixiv +phil noto, peter mohrbacher, thomas kinkade, artgerm, 1 9 5 0 s rockabilly anya taylor - joy catwoman dc comics, symmetrical eyes, city rooftop +dnd character concept portrait, angry male elf druid in forest, detailed, high quality, dynamic lighting, fantasy, artwork by artgerm, wlop, alex ross, greg rutknowski, alphonse mucha +a king with a skull head, in the style of artgerm, charlie bowater, atey ghailan and mike mignola, vibrant colors and hard shadows and strong rim light, plain background, comic cover art, trending on artstation +With the spikes in her hair +venus, the empress, wearing a magnificent dress, sitting on a divan in the middle of a beautiful green plains full of little flowers. intricate, elegant, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, by justin gerard and artgerm, 8 k +beautiful apocalyptic woman with pink Mohawk, standing on mad max panzer tank, 4k ultra hd, fantasy dark art, tank girl, artgerm, concept art, artstation, octane render, elegant, detailed digital painting +i crave only the cold clean certainty of steel and silicon, trending on artstation +nikola tesla, lightning, portrait, sharp focus, digital art, concept art, dynamic lighting, epic composition, colorful, trending on artstation, by emylie boivin 2. 0, rossdraws 2. 0 +professional concept art of a symmetrical ominous floating terrifying thing in a dark room by artgerm and greg rutkowski ( thin white border ). an intricate, elegant, highly detailed digital painting, concept art, smooth, sharp focus, illustration, in the style of cam sykes, wayne barlowe, igor kieryluk. +beautiful lifelike award winning marble statue bust of tsunku trending on art station artgerm greg rutkowski alphonse mucha museum quality cinematic atmospheric +steampunk robot ant, unreal engine realistic render, 8 k, micro detail, intricate, elegant, highly detailed, centered, digital painting, artstation, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +mtg character portrait of a brawny male leonin warrior african lion angel of justice, with fiery golden wings of flame, wearing shining armor, wielding flaming sword and holding large fiery shield, by peter mohrbacher, wadim kashin, greg rutkowski, larry elmore, george pemba, ernie barnes, raymond swanland, magali villeneuve, trending on artstation +dynamic portrait painting of Michael Myers sitting in the waiting room of an optometrist amongst other normal patients, sharp focus, face focused, trending on ArtStation, masterpiece, by Greg Rutkowski, by Ross Tran, by Fenghua Zhong, octane, soft render, oil on canvas, moody lighting, high contrast, cinematic, professional environmental concept art +Concept art of male high elf with light blue hair, black leather armor, golden eagle skull on chest, by Naranbaatar Ganbold, trending on artstation +a closeup portrait of a mia khalifa, dramatic light, lake background, sunset, dark, painted by stanley lau, painted by greg rutkowski, painted by stanley artgerm, digital art, trending on artstation +portrait of salman rushdie, deep focus, d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +anime key visual of beautiful elizabeth olsen police officer, cyberpunk, futuristic, stunning features, perfect face, high details, digital painting, artstation, smooth, soft focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +Beautiful portrait of an attractive Persian Princess who is an architect, beautiful princess, face painting, dramatic lighting, intricate, wild, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, footage from space camera +full body portrait character concept art, anime key visual of a little witch with her capybara mascot, trending on pixiv fanbox, painted by makoto shinkai takashi takeuchi studio ghibli +perfectly-centered-Portrait of the most beautiful people on the planet, river, washing clothes, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K, art by artgerm and greg rutkowski and alphonse mucha +wide shot of vietnamese solider girl, green uniform, burning city in the background, epic, elder scrolls art, fantasy, skyrim, hd shot, digital portrait, beautiful, artstation, by artgerm, guy denning, jakub rozalski, magali villeneuve and charlie bowater +apocalyptic city, digital painting, artstation, concept art, donato giancola, Joseph Christian Leyendecker, WLOP, Boris Vallejo, Breathtaking, 8k resolution, extremely detailed, beautiful, establishing shot, artistic, hyperrealistic, octane render, cinematic lighting, dramatic lighting, masterpiece, light brazen +male dracula rollerskating with rollerskates in a roller rink by charlie bowater and titian and artgerm, full body portrait, intricate, face, elegant, beautiful, highly detailed, dramatic lighting, sharp focus, trending on artstation, artstationhd, artstationhq, unreal engine, 4 k, 8 k +a dark forest where gears and electronic parts grow on the trees tops, cyberpunk landscape wallpaper, d&d art, fantasy, painted, 4k, high detail, sharp focus +Photorealistic elvish goddess in a magical bioluminescent forest Hyperdetailed photorealism, 108 megapixels, amazing depth, glowing rich colors, powerful imagery, psychedelic Overtones, 3D finalrender, 3d shading, cinematic lighting, artstation concept art +portrait of a demon, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha and william - adolphe bouguereau +the man stuck in the wall, creepy explorer sketch, godlike design, concept art, beyond the void, grand scale, intricate detailed +Very very very very highly detailed epic central composition studio photography of face with venetian mask, intricate, dystopian, sci-fi, extremely detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, intimidating lighting, incredible art by Anna Dittmann and Jesper Ejsing and Anton Pieck +water, glowing lights!! intricate elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by greg rutkowski +highly detailed portrait of Eminem wearing a beret and gold chains and brandishing a pistol, big eyes, realistic portrait, symmetrical, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +robocop torso, symmetry, faded colors, exotic alien features, cypherpunk background, tim hildebrandt, wayne barlowe, bruce pennington, donato giancola, larry elmore, masterpiece, trending on artstation, featured on pixiv, cinematic composition, beautiful lighting, sharp, details, hyper detailed, 8 k, unreal engine 5 +Boris Johnson as Neo from Matrix, black sunglasses, realistic portrait, symmetrical, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +a copic maker sketch of a stewardess girl wearing kikyo's clothing designed by balenciaga by john berkey by stanley artgerm lau, greg rutkowski, thomas kinkade, alphonse mucha, loish, norman rockwell +a matte painting of a man sitting down and having a cup of tea in his house by the beach, in the style of artgerm, charlie bowater, atey ghailan and mike mignola, vibrant colors and hard shadows and strong rim light, plain background, comic cover art, trending on artstation +a smug exclusivists female, black ink line art and watercolor, intricate, digital painting, concept art, smooth, focus, rim light style tim burton +3 5 mm portrait of samurai in training dojo, in the style of david cronenberg, scary, weird, high fashion, id magazine, vogue magazine, surprising, freak show, realistic, sharp focus, 8 k high definition, film photography, photo realistic, insanely detailed, intricate, by david kostic and stanley lau and artgerm +rats fixing cars in the garage, key visual, a fantasy digital painting by makoto shinkai and james gurney, trending on artstation, highly detailed +photo of a gorgeous sultry young woman in the style of David la chapelle , realistic, sharp focus, 8k high definition, 35mm film photography, photo realistic, insanely detailed, intricate, elegant, art by David kostic and stanley lau and artgerm +sliced coconut, electronics, ai, cartoonish cute, pine trees, dramatic atmosphere, trending on artstation, 3 0 mm, by noah bradley trending on artstation, deviantart, high detail, stylized portrait +360 degree equirectangular, anthropomorphic family of mushrooms, family portrait, Art Deco nature, mystical fantasy, Pixar cute character design, intricate art deco mushroom patterns, elegant, sharp focus, 360 degree equirectangular panorama, art by Artgerm and beeple and Greg Rutkowski and WLOP, 360 monoscopic equirectangular +portrait of othinus from toaru, anime fantasy illustration by tomoyuki yamasaki, kyoto studio, madhouse, ufotable, trending on artstation +a portrait of a evil cybernetic magician in glass armor releasing spell, full height, moving forward, cyberpunk concept art, trending on artstation, highly detailed, intricate, sharp focus, digital art, 8 k +Portrait of the black dragon Alduin breathing a rainbow-colored fire. 4k. Concept art. High detail. Unreal engine. +Greg Manchess portrait painting of Ganon from Legend of Zelda as Overwatch character, medium shot, asymmetrical, profile picture, Organic Painting, sunny day, Matte Painting, bold shapes, hard edges, street art, trending on artstation, by Huang Guangjian and Gil Elvgren and Sachin Teng +a hyper - realistic character concept art portrait of emilia clarke, depth of field background, artstation, award - winning realistic sci - fi concept art by jim burns and greg rutkowski, beksinski, a realism masterpiece, james gilleard, bruegel, alphonse mucha, and yoshitaka amano. +Wide shot of a chrome spaceship in battle, explosions and purple lasers. Asteroid belt. Scenic view, in the void of space, underexposed, matte painting by Craig mullins and Emmanuel_Shiu and john berkey, cinematic, dark sci-fi, concept art trending on artstation, 4k, insane details, ultra realistic +ebony beauty portrait, black red smoke, ink, stylized tattoos, draconic priestess, portrait by Artgerm, peter mohrbacher +leonine devil in flowing robes, ethereal, backlit, high fantasy, highly detailed, puzzled expression, realistic lighting, sharp focus, intricate, by artgerm, wlop, crossdress, frank frazetta, trending on artstation +giant magical floating golden sun, bright godrays, vibrant colors, by sylvain sarrailh, rossdraws, ambient light, ultra detailed, fantasy artwork, 8 k, volumetric lighting, trending on artstation, award winning, beautiful scenery, very beautiful. +a 3 d render of a stack of green cubes on the left and an orange ball on the right in a red room, blender, ue 5, octane render, trending on artstation +ori and the olw, close up bokeh hiperrealistic, high detailled, darkness dramatic, sharp focus, octane render, imax +richly detailed color illustration of a fiending-addict-seeking-at-the-doctors-office illustrated by Artgerm and Mina Petrovic and Timothy Kong and Marina Federovna. 3D shadowing +a study of cell shaded portrait of Dora the Explorer as a Borderlands 3 character, llustration, post grunge, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus, alien, Trending on Artstation, HQ, deviantart, art by artgem +A beautiful female warrior holding a bow an arrow wearing a magical bikini posing on a rock in a magical forest, super detailed and realistic face, fantasy art, in the style of Artgerm, illustration, epic, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing, vibrant +Lofi Steampunk Bioshock portrait, Pixar style, by Tristan Eaton Stanley Artgerm and Tom Bagshaw +a beautiful hyperdetailed highly detailed urbex industrial architecture tower nature building unfinished building by zaha hadid, retro sunset retrowave darkacademia at fall hyperrealism cgsociety tokyo at night thermal vision, archdaily, wallpaper, highly detailed, trending on artstation. +Cyborg woman sitting on a chair in a futuristic room smoking a cigar, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K, art by artgerm and greg rutkowski and alphonse mucha +young angry woman, beautiful girl, full body, explosive hair, cowboy hat, realistic, serov, surikov, vasnetsov, repin, kramskoi, insanely detailed, charlie bowater, tom bagshaw, high resolution, octane rendered, unreal engine, illustration, trending on artstation, masterpiece, 8 k +an anime landscape of a girl wearing a kimono, near the river in a japanese summer festival from skyrim, by stanley artgerm lau, wlop, rossdraws, james jean, andrei riabovitchev, marc simonetti, and sakimichan, trending on artstation +highly detailed portrait of a man with a handsaw head by greg rutkowski and fujimoto tatsuki, dramatic lighting, dynamic pose, dynamic perspective +film noir woman, character sheet, concept design, contrast, hot toys, kim jung gi, greg rutkowski, zabrocki, karlkka, jayison devadas, trending on artstation, 8 k, ultra wide angle, pincushion lens effect +portrait of a diabolical marble stone cyborg, wearing torn white cape, dynamic pose, glowing eyes, post apocalyptic ancient ruins, glowing veins subsurface scattering, in clouds, sunset, portrait, by gerald brom, by mikhail vrubel, by peter elson, muted colors, extreme detail, trending on artstation, 8 k +portrait of donald trump, soft hair, muscular, half body, leather, hairy, d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +black super hero girl | very very anime!!!, fine - face, beyonce, realistic shaded perfect face, fine details. anime. realistic shaded lighting poster by ilya kuvshinov katsuhiro otomo ghost - in - the - shell, magali villeneuve, artgerm, jeremy lipkin and michael garmash and rob rey +richly detailed color illustration of a nerd-core-instructional-video illustrated by Artgerm and Mina Petrovic and Timothy Kong and Marina Federovna. 3D shadowing +a group of spanish trap singers drinking red wine, oil painting by alex katz, trending on artstation +photorealistic beautiful ethereal natalie portman in the style of michael whelan and greg rutkowski. hyperdetailed photorealism, 1 0 8 megapixels, amazing depth, glowing rich colors, powerful imagery, psychedelic overtones, 3 d finalrender, 3 d shading, cinematic lighting, artstation concept art +a cute pet by neville page, ken barthelmey, carlos huante and doug chiang, sharp focus, trending on artstation, hyper realism, octane render, 8 k, hyper detailed, ultra detailed, highly detailed, zbrush, concept art, creature design +very cute illustration for a children's book, digital art, detailed, rim light, exquisite lighting, clear focus, very coherent, details visible, soft lighting, character design, concept, atmospheric, dystopian, trending on artstation, fog, sun flare +Still of a humanoid robot painting on a canvas, high detail, cinematic, , science fiction concept art by Greg Rutkowski and Moebius and Le Corbusier +asymmetrical!! long shot of a snufkin smoking a pipe, nebula, intricate, elegant, highly detailed, digital painting, artstation, biolusence, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, horizon zero dawn 8 k +portrait of red - tinged, red leds, futuristic cybernetic warrior alien in profile, highly intricate, detailed humanoid, trending on artstation +of a beautiful scary Hyperrealistic stone castle on top of a hill in the middle of a dark and creepy forest, macro lens, highly detailed, digital painting, trending artstation, concept art, illustration, cinematic lighting, vibrant colors, photorealism, epic, octane render +piles of modular synth cables mixed with mangrove roots mixed with old video game consoles, puerto rican grafitti goddess chilling out wearing a headpiece made of circuit boards, by cameron gray, wlop, stanley kubrick, masamune, unique perspective, epic, trending on artstation, photorealistic, 3 d render, vivid +oil painting portrait of a young woman with long flowing hair in a white dress, dancing through a field of flowers at sunset with mountains in the background, hazy, digital art, chiaroscuro, artstation, cinematic, golden hour, digital art painting by greg rutkowski, william - adolphe bouguereau, hazy atmosphere, flowers, cinematic lighting +dark wizard of forest, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +photorealistic dog piloting a biplane. hyperdetailed photorealism, 1 0 8 megapixels, amazing depth, glowing rich colors, powerful imagery, psychedelic overtones, 3 d finalrender, 3 d shading, cinematic lighting, artstation concept art +clear portrait of tony soprano, cottagecore!!, mafia background hyper detailed, character concept, full body, dynamic pose, intricate, criminal appearance, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +fantasy art of glowing goldfish swimming in the air, in the streets of a japanese town at night, with people watching in wonder, by fenghua zhong, highly detailed digital art, trending on artstation +fantasy steps with pillars on both sides by greg rutkowski +award winning digital portrait of a feminine attractive male jester at a magnificent circus, beautiful circus themed background with soft colors and lights, trending artstation, digital art, aesthetic, bloom, intricate, elegant, sharp focus, digital illustration, highly detailed, octane render, digital painting, concept art, fantasy, masterpiece, by lisa buijteweg and sakimichan +a ultradetailed beautiful concept art of an old mind key, with intricate detail, oil panting, high resolution concept art, 4 k, by artgerm +Ogun with large iron spears, he has tribal face markings and war paint, bronze-brown skin with african features and strong jaw line prominent brow and menacing look, wearing tribal armor, medium shot digital illustration trending on artstation by artgerm, face by wlop +full face shot of rimuru tempest, sky blue straight hair, long bangs, with amber eyes, gold eyes, wearing a black jacket, high collar, ultra detailed, concept art, award winning photography, digital painting, cinematic, wlop artstation, closeup, pixiv, evil, yoshitaka amano, andy warhol, ilya kuvshinov, +Moon Knight mixed with Goku, RPG Reference, art by ilya kuvshinov, artgerm, Alphonse mucha, and Greg Rutkowski, Trending on Artstation, octane render, Insanely Detailed, 8k, HD +portrait of the cutest red fox ever, fluffy, cinematic view, epic sky, detailed, concept art, low angle, high detail, warm lighting, volumetric, godrays, vivid, beautiful, trending on artstation, by jordan grimmer, huge scene, grass, art greg rutkowski +bandit, ultra detailed fantasy, elden ring, realistic, dnd, rpg, lotr game design fanart by concept art, behance hd, artstation, deviantart, global illumination radiating a glowing aura global illumination ray tracing hdr render in unreal engine 5 +ilya kuvshinov with blue hair, yellow irises, professional digital painting, concept art, unreal engine 5, 8 k, cinematic, wlop, tendrils in the background, art by greg rutkowski, pixiv art, junji ito, yoshitaka amano +high resolution concept art of naruto and yoda kissing in paris +character concept portrait of a stoic and proud woman in an elegant gown, pale face, intricate, elegant, digital painting, concept art, smooth, sharp focus, illustration, from Metal Gear, by Ruan Jia and Mandy Jurgens and William-Adolphe Bouguereau, Artgerm +symmetry!! portrait of skull, sci - fi, glowing lights!! intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, 8 k +realistic portrait of beautifully crystalized and detailed portrait of a biomech zombie woman wearing a gasmask, matte painting of cinematic movie scene red dragon, horror, created by gustave dore and greg rutkowski, high detailed, smooth draw, synthwave neon retro, intricate, realistic proportions, dramatic lighting, trending on artstation. +a portrait of sexy lady casting ice - ball and shoot it, cyberpunk concept art, trending on artstation, highly detailed, intricate, sharp focus, digital art, 8 k +close up shot of a full body floating astronaut portrait smoke elemental fading into white smoke, high contrast, james gurney, peter mohrbacher, mike mignola, black paper, mandelbulb fractal, trending on artstation, exquisite detail perfect, large brush strokes, bold pinks and blues tones, intricate ink illustration, black background +beautiful blonde teenage boy assassin, wearing leather jacket, beautiful, detailed portrait, cell shaded, 4 k, concept art, by wlop, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality +commission of a robot chasing thugs.dramatic,character design by charles bowater,greg rutkowski,ross tran,hyperdetailed,hyperrealistic,4k,deviantart,artstation,professional photography,concept art,dramatic +foggy neon night, sayaka isoyama leaning back against a wall in a black minidress smoking a cigarette outside a neon lit entrance, 1 9 7 0 s, intricate, moody, tasteful, intimate, highly detailed, short focus depth, artgerm, donato giancola, joseph christian leyendecker +concept art of a shalltear bloodfallen and vladimir volegov and alexander averin and delphin enjolras and daniel f. gerhartz +of a dark and stormy ocean with large strange cute water creatures with big eyes, mouth and round teeth appearing from the water, in the style of Gaudi, macro lens, shallow depth of field, highly detailed, digital painting, trending artstation, concept art, illustration, cinematic lighting, vibrant colors, photorealism, epic, octane render +cat with lute, sitting in the rose garden, medieval portrait, concept art, close up +harry styles as miley cyrus riding a wrecking ball, high octane render, digital art trending on artstation +loch ness monster by charlie bowater and titian and artgerm, full - body portrait, intricate, face, lake, elegant, green mist, beautiful, highly detailed, dramatic lighting, sharp focus, trending on artstation, artstationhd, artstationhq, unreal engine, 4 k, 8 k +a full body portrait of a young latin woman in a flowery fruit - based dress, with a greek mask on her head, night lighting with candles delicate features finely detailed perfect art, at an ancient city, gapmoe yandere grimdark, trending on pixiv fanbox, painted by greg rutkowski makoto shinkai takashi takeuchi studio ghibli +ultra realistic illustration, young man with dark gray skin, short white hair, intricate, with dark clothes, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +concept art by jama jurabaev, cel shaded, cinematic shot, trending on artstation, high quality, brush stroke, hyperspace, vibrant colors, portrait of rick grimes +a beautiful portrait of a pearl goddess with glittering skin, a detailed painting by greg rutkowski and raymond swanland, featured on cgsociety, fantasy art, detailed painting, artstation hd, photorealistic +of a advertisement with a scene of a highway with words written on the road in front of the viewer, occlusion shadow, specular reflection, rim light, unreal engine, octane render, artgerm, artstation, art jiro matsumoto, high quality, intricate detailed 8 k, sunny day +best book cover design, glowing silver and golden elements, full close-up portrait of realistic crow with gems, book cover, green forest, white moon, establishing shot, extremly high detail, photo-realistic, cinematic lighting, by Yoshitaka Amano, Ruan Jia, Kentaro Miura, Artgerm, post processed, concept art, artstation, matte painting, style by eddie mendoza, raphael lacoste, alex ross +a girl is running, sport clothing, fitness watch, anime style, brown short hair, hair down, symmetrical facial features, from arknights, hyper realistic, rule of thirds, extreme detail, 4 k drawing, trending pixiv, realistic lighting, by alphonse mucha, greg rutkowski, sharp focus, backlit +a hyper realistic professional photographic picture of dragon hotdog, photographic filter unreal engine 5 realistic hyperdetailed 8k ultradetail cinematic concept art volumetric lighting, digital artwork, very beautiful scenery, very realistic painting effect, hd, hdr, cinematic 4k wallpaper, 8k, ultra detailed, high resolution +A portrait of a male elf, 20 years old, short silver hair, red eyes, wearing a spiked black metal crown, black heavy armor with gold trim, and a red cape, lean but muscular, attractive, command presence, royalty, weathered face, smooth, sharp focus, illustration, concept art, highly detailed portrait muscle definition, fantasy painting, ArtStation, ArtStation HQ +2 8 mm macro headshot of a ethereal magical young winged fairy princess wearing a white robe in a fantasy garden, d & d, fantasy, intricate, rim light, god rays, volumetric lighting, dark souls, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, orthodoxy, art by greg rutkowski, maxfield parrish and alphonse mucha, new art nouveau, soft lighting, tarot card +portrait of sansa stark with crown, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha and william - adolphe bouguereau +lush solarpunk Victorian windowsill with futuristic plants on it, looking out toward a solarpunk cityscape, vignette of windowsill, detailed digital concept art by anton fadeev and marc simonetti, trending on artstation +a portrait of a beautiful biomechanical queen of necropolis, horror concept art by giger and beksinski and szukalski and wlop and pete mohrbacher, digital art, highly detailed, intricate, sci-fi, sharp focus, Trending on Artstation HQ, deviantart, unreal engine 5, 4K UHD image +ocean of canvas that catches liquid fire, intricate pearls, ornate ruby, magical, concept art, art nouveau, Reylia Slaby, Peter Gric, trending on artstation, volumetric lighting, CGsociety +incredible, refugees crossing a mindblowingly beautiful bridge made of rainbow, energy pulsing, hardlight, matte painting, artstation, solarpunk metropolis, cgsociety, dramatic lighting, vibrant greenery, concept art, octane render, arnold 3 d render +bemused to be soon consumed by a tentacle demon, in a leather neck restraint, beautiful young woman with medium length silky black hair in a black silk tank top in a full frame zoom up of her face and neck in complete focus, looking upwards in a room of old ticking clocks, complex artistic color ink pen sketch illustration, subtle detailing, gentle shadowing, fully immersive reflections in her eyes, concept art by Artgerm and Range Murata in collaboration. +baby yoda, portrait, concept art by doug chiang cinematic, realistic painting, high definition, concept art, portait image, path tracing, serene landscape, high quality, highly detailed, 8 k, soft colors, warm colors, turbulent sea, high coherence, anatomically correct, hyperrealistic, concept art, defined face, symmetrical 5 +isometric 3D of the ethereum symbol in gold and black by artgerm and greg rutkowski, alphonse mucha, cgsociety and beeple highly detailed, sharp focus, cinematic lighting, illustration, art, octane render, Unreal Engine Lumen, very coherent. cinematic, hyper realism, high detail, octane render, 8k +giant snake on a moonlit desert, fantasy, d & d, art by artgerm and greg rutkowski, cinematic shot, intricate, ornate, photorealistic, ultra detailed, trending artstaition, realistic, 1 0 0 mm, photography, octane, high definition, depth of field, bokeh, 8 k +a beautiful portrait of a skull goddess by Greg Rutkowski and Raymond Swanland, Trending on Artstation, ultra realistic digital art +a whirlwind of souls rushing inside the metaverse, half body, glowin eyes, insect, lizard, d & d, fantasy, intricate, elegant, highly detailed, colorful, vivid color, digital painting, artstation, concept art, art by artgerm and greg rutkowski and alphonse mucha and ruan jia +medieval knight power armour, 4 0 k, space marine, concept art, medieval, fantasy, cinematic lighting, detailed digital matte painting in the style of simon stalenhag and bev dolittle zdzislaw beksinski, greg hildebrandt artstation +portrait of burning woman, fire, blood red eyes, open mouth, vampire fangs, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, octane render, unreal engine, art by aenaluck and roberto ferri and greg rutkowski, epic fantasy, digital painting +portrait of a beautiful mysterious woman warrior wearing an armour costume, holding a bouquet of flowing flowers, hands hidden under the bouquet, fantasy, regal, intricate, by stanley artgerm lau, greg rutkowski, thomas kinkade, alphonse mucha, loish, norman rockwell +a wholesome animation key shot of a band behemoth performing on stage, medium shot, studio ghibli, pixar and disney animation, 3 d, sharp, rendered in unreal engine 5, anime key art by greg rutkowski, bloom, dramatic lighting +dungeons and dragons old evil wizard character closeup portrait, dramatic light, lake background, 2 0 0 mm focal length, painted by stanley lau, painted by greg rutkowski, painted by stanley artgerm, digital art, trending on artstation +scenery from game of thrones, wide angle, super highly detailed, professional digital painting, artstation, concept art, smooth, sharp focus, no blur, no dof, extreme illustration, unreal engine 5, photorealism, hd quality, 8 k resolution, cinema 4 d, 3 d, beautiful, cinematic, art by artgerm and greg rutkowski and alphonse mucha and loish and wlop +female elf bard, Jade, dungeons and dragons, amazing detail, character concept art, illustration, fantasy, 4k +detailed coffee table in the vaporwave mid century modern livingroom. highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +retrofuturistic portrait of a uyghur prisoner in a tracksuit that's dirty and ripped, close up, wlop, dan mumford, artgerm, liam brazier, peter mohrbacher, jia zhangke, 8 k, raw, featured in artstation, octane render, cinematic, elegant, intricate, 8 k +3 / 4 view of a portrait of pixie woman with bat wings, confident pose, pixie, genshin impact,, intricate, elegant, sharp focus, illustration, highly detailed, concept art, matte, trending on artstation, anime, art by wlop and artgerm and greg rutkowski, strong brush stroke, sharp focus, illustration, morandi color scheme, art station, by ilya kuvshinov h 6 4 0 +high angle photo of a gorgeous big chungus in the style of stefan kostic, realistic, sharp focus, 8 k high definition, insanely detailed, intricate, elegant, art by stanley lau and artgerm +A highly detailed matte oil painting of a forest by Mokoto Shinkai, hyperrealistic, breathtaking, beautiful composition, by Artgerm, by beeple, by Studio Ghibli, cinematic lighting, octane render, 4K resolution, trending on artstation +realistic detailed image of a dark figure screaming on a wooden cross in the middle of a busy city street in the style of francis bacon, hooded figure surreal, norman rockwell and james jean, greg hildebrandt, and mark brooks, triadic color scheme, by greg rutkowski, in the style of francis bacon and syd mead and edward hopper and norman rockwell and beksinski, dark surrealism, open ceiling, highly detailed, painted by francis bacon, painted by james gilleard, surrealism, by nicola samori, airbrush, ilya kuvshinov, wlop, stanley artgerm, very coherent, art by takato yamamoto and james jean +a photorealistic dramatic hyperrealistic render of a beautiful mazinger z by go nagai, wlop, greg rutkowski, alphonse mucha, beautiful dynamic dramatic dark moody lighting, shadows, cinematic atmosphere, artstation, concept design art, octane render, 8 k +a portrait of the most beautiful woman in the world with long black hair that extends past her waist with locks of hair that frame her face down to her chin and shows off her high forehead, dark brown eyes with long, voluminous eyelashes and pale skin, narrow waist and very large chest, wearing a revealing red V-neck blouse a loose sarong with the green symbol of the Kuja adorned on it, along with a white cape sporting epaulettes more commonly found on the jackets of high-ranking Marines, and red high heel pumps, pink hearts in the background , romantic themed, beautiful face, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration +portrait of melted zeus starring into the camera, fixed eyes, lightning environment, surreal, dramatic lighting, face, detailed, intricate, elegant, highly detailed, digital painting, artstation,, concept art, smooth, sharp focus, illustration, art by sam spratt, dan mumford, artem demura and alphonse mucha +portrait painting of a punk elven bard with green eyes and snow white fur, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and charlie bowater and magali villeneuve and alphonse mucha +gigachad jill valentine bodybuilder jumping from a building fighting in racoon city, fantasy character portrait, ultra realistic, anime key visual, full body concept art, intricate details, highly detailed by greg rutkowski, ilya kuvshinov, gaston bussiere, craig mullins, simon bisley +inside a medieval hobbit home, ornate, beautiful, atmosphere, vibe, mist, smoke, chimney, rain, well, wet, pristine, puddles, red speckled mushrooms, waterfall, melting, dripping, snow, creek, lush, ice, bridge, cart, bonzai, green, stained glass, forest, flowers, concept art illustration, color page, 4 k, tone mapping, doll, akihiko yoshida, james jean, andrei riabovitchev, marc simonetti, yoshitaka amano, digital illustration, greg rutowski, volumetric lighting, sunbeams, particles, trending on artstation +girl with super long hair, hair becoming autumn red leaves, intricate, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +a phantom undead mage ape with whirling galaxy around, tattoos by anton pieck, intricate, extremely detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, intimidating lighting, incredible art, +a stunningly detailed picture of indoor botanical garden , girl, by greg rutkowski and thomas kinkade, trending on artstation +death is swallowed up in victory, very detailed and beautiful portrait of a young woman by daniel oldenburg, necromancer bt h. r. giger, screaming with fear, artwork by artgerm, centered shot, wide angle, full body, islandpunk, solarpunk, fantasy, highly detailed, digital painting, artstation, smooth, sharp focus, landscape art by thomas kinkade and yusei uesugi +a painting of the most beautiful spaceship, an exquisite and beautiful rendition, by greg rutkowski +3d infrared octane render concept art by Mo Xiang Tong Xiu, by Igarashi Daisuke, by makoto shinkai, cute beauty cozy portrait anime sad schoolgirls under dark pink and blue tones, mirror room. light rays. deep water bellow. realistic 3d face. dramatic deep light, trending on artstation, oil painting brush +anthropomorphic art of a timelord owl inside tardis, victorian inspired clothing by artgerm, victo ngai, ryohei hase, artstation. fractal papersand books. highly detailed digital painting, smooth, global illumination, fantasy art by greg rutkowsky, karl spitzweg, doctor who +otters playing poker, hyper detailed, dramatic lighting, cgsociety, realistic, hyper detailed, insane details, intricate, dramatic lighting, hypermaximalist, golden ratio, rule of thirds, octane render, weta digital, micro details, ultra wide angle, artstation trending, 8 k, +hieronymus bosch, greg rutkowski, anna podedworna, painting of chris farley in his academy award winning role +baroque rococo futuristic aristocrat, d & d, fantasy, portrait, highly detailed, digital painting, trending on artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and magali villeneuve +full body pose, hyperrealistic photograph of inner peace, dim volumetric lighting, 8 k, octane beautifully detailed render, extremely hyper detailed, intricate, epic composition, cinematic lighting, masterpiece, trending on artstation, very very detailed, stunning, hdr, smooth, sharp focus, high resolution, award, winning photo, dslr, 5 0 mm +painting of hybrid hamster and gecko!!!!, intercrossed animal, crossbred, by zdzislaw beksinski, by lewis jones, cold hue's, warm tone gradient background, concept art, digital painting +Given,' Fivetide said, nodding his eye stalks, re-winding his harpoon cable, lifting a piece of meat from his own plate to his beak, reaching for a drink and drumming one tentacle on the table with everybody else as one of the scratchounds got another on its back and bit its neck out. 'Good play! Good play! Seven; that's my dog! Mine; I bet on that! I did! Me! You see, Gastrees? I told you! Ha ha ha! Sci-fi, sunrise, concept art, octane render, unreal engine 5, trending on Artstation, high quality, highly detailed, 8K, soft lighting, godrays, path tracing, serene landscape, turbulent sea, high coherence, anatomically correct, hyperrealistic, sand, beautiful landscape, cinematic, +fantasy art of a bustling tavern in china, at night, by fenghua zhong, highly detailed digital art, trending on artstation +portrait painting of elizabeth olsen wanda maximoff with green skin and pointy ears wearing sci - fi clothes, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and charlie bowater and magali villeneuve and alphonse mucha +a portrait of tony stark, fantasy, sharp focus, intricate, elegant, digital painting, artstation, matte, highly detailed, concept art, illustration, ambient lighting, art by ilya kuvshinov, artgerm, alphonse mucha, and greg rutkowski +portrait of the two most beautiful women surrounded by soft florals, vaporwave lighting, dewy skin, concept art, high detail, beautiful, dreamy +a beautiful portrait painting of a ( ( cyberpunk ) ) girl by simon stalenhag and pascal blanche! and alphonse mucha! and nekro!!. in style of digital art. colorful comic, film noirs!, symmetry, hyper detailed. octane render. trending on artstation +emma thompson as an angel standing in the front of gates of hell. angel is draped with bones. digital painting. art station. mood lighting. skindness, highly detailed, concept art, intricate, sharp focus, einar jonsson and bouguereau - h 1 2 0 0 +tyrion lannister working in a winery, animation pixar style, by magali villeneuve, artgerm, jeremy lipkin and michael garmash, rob rey and kentaro miura style, golden ratio, trending on art station +a dramatic, epic, ethereal painting of a !handsome! (very thicc) mischievous shirtless cowboy with a beer belly wearing a large belt and bandana offering a whiskey bottle | he is relaxing by a campfire | background is a late night with food and jugs of whisky | homoerotic | stars, tarot card, art deco, art nouveau, mosaic, intricate | by Mark Maggiori (((and Alphonse Mucha))) | trending on artstation +anime character portrait of a female martial artist!! elegant, intricate outfit, fine details by stanley artgerm lau, wlop, rossdraws, james jean, andrei riabovitchev, marc simonetti, and sakimichan, trembling on artstation +portrait of green anthropomorphic mantis religiosa ; hard predatory look ; d & d rogue ; powerful front forelegs holding an enchanted dagger ; flat triangle - shaped head with antennae and compound eyes ; concept art ; artstation ; 8 k ; wallpapers ; heavy contrast ; cinematic art ; cgsociety ; high coherence ; golden ratio ; rule of thirds ; art by greg rutkowski and artgerm +close up Portrait of elizabeth olsen as real life beautiful young teen girl wearing assamese bihu mekhela sleeveless silk saree and gamosa in Assam tea garden, XF IQ4, 150MP, 50mm, F1.4, ISO 1000, 1/250s, attractive female glamour fashion supermodel photography by Steve McCurry in the style of Annie Leibovitz, face by Artgerm, daz studio genesis iray, artgerm, mucha, bouguereau, gorgeous, detailed anatomically correct face!! anatomically correct hands!! amazing natural skin tone, 4k textures, soft cinematic light, Adobe Lightroom, photolab, HDR, intricate, elegant, highly detailed,sharp focus +digital character concept art by artgerm and greg rutkowski and alphonse mucha. clear portrait of a young wife blessed by god to uncontrollably become overwhelmingly perfect!! blonde, clothed! obviously feminine holy body!! light effect. hyper detailed, glowing lights!! intricate, elegant, digital painting, artstation, smooth, sharp focus +Gandalf, 4k oil on linen by wlop, artgerm, andrei riabovitchev, nuri iyem, james gurney, james jean, greg rutkowski, highly detailed, soft lighting 8k resolution +Cyborg biomechanical jellyfish deity, sci-fi, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +man male demon, full body white purple cloak, warlock, character concept art, costume design, illustration, black eyes, white horns, trending on artstation, Artgerm +baroque bedazzled gothic royalty frames surrounding a pixelsort rimuru tempest smiling, sky blue straight hair, bangs, with amber eyes, yellow golden eyes, wearing a black maximalist spiked jacket, high collar, ultra detailed, concept art, digital painting, pretty, cinematic, wlop artstationin wonderland, sharpened early computer graphics, remastered chromatic aberration +close up portrait of a ghost in the mountains of hell, oil painting by tomasz jedruszek, cinematic lighting, pen and ink, intricate line, hd, 4 k, million of likes, trending on artstation +A Snowplow clearing a beautiful snowy landscape with a small hut in the background. A blizzard and heavy snow falls. Fog and mist, highly detailed, concept art, digital art, 4k +closeup portrait shot of a cyberpunk child in a scenic dystopian environment, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +portrait of a girl by ayami kojima, mixture between russian and japanese, she is about 2 0 years old, black bob hair, very tall and slender, she is wearing a steampunk tactical gear, highly detailed portrait, digital painting, artstation, concept art, smooth, sharp foccus ilustration, artstation hq +a humanoid cello warrior, Character design, concept art +astronaut holding a flag in an underwater desert. a submarine is visible in the distance. dark, concept art, cinematic, dramatic, atmospheric, 8 k, trending on artstation, blue, fish, low visibility, light rays, extremely coherent, bubbles, fog, ocean floor, christopher nolan, interstellar, finding nemo +engine room on a starship,, star - field and planet in the background, digital art, highly detailed, trending on artstation, sci - fi +a portrait of a beautiful bikini model, art by lois van baarle and loish and ross tran and rossdraws and sam yang and samdoesarts and artgerm, digital art, highly detailed, intricate, sharp focus, Trending on Artstation HQ, deviantart, unreal engine 5, 4K UHD image +concept art oil painting by Jama Jurabaev, extremely detailed, brush hard, artstation, for AAA game, high quality +grey wizard casting a spell, details face, photo, bloody eyes, unreal engine, by popular digital artist, digital, artstation, detailed body, heavenly atmosphere, digital art, overdetailed art, trending on artstation, cgstudio, the most beautiful image ever created, dramatic, award winning artwork, beautiful scenery +schoolgirl with blonde twintails | very very anime!!!, fine - face, audrey plaza, realistic shaded perfect face, fine details. anime. realistic shaded lighting poster by ilya kuvshinov katsuhiro otomo ghost - in - the - shell, magali villeneuve, artgerm, jeremy lipkin and michael garmash and rob rey +kate beckinsdale comic cover art, artgerm, joshua middleton, pretty stella maeve witch doing black magic, serious look, purple dress, symmetrical eyes, symmetrical face, long black hair, full body, twisted evil dark forest in the background, cool colors +portrait painting of an elven galadrial beautiful women with dark shiny moon hair and gold sigils and thin arcane glyph's tattooed on her cheekbone, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and charlie bowater and magali villeneuve and alphonse mucha +portrait painting of an elven eladrin young man with short light orange hair and freckles and tribal tattoos on his cheekbones wearing fur armor, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and charlie bowater and magali villeneuve and alphonse mucha +portrait of kim wexler and saul goodman from better call saul. colourful suit, garish tie. oil painting elegant, highly detailed, centered, digital painting, artstation, concept art, hyperrealistic, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, ilya repin, drew struzan +old arnold schwarzenegger as a roman gladiator, fantasy, intricate, artstation, full body, concept art, smooth, sharp focus by huang guangjian and gil elvgren and sachin teng, 8 k +special forces soldier with ukrainian blue yellow flag standing alone on a huge pile of human skulls as a winner, masculine figure, d & d, fantasy, bright atmosphere, volumetric lights, intricate, elegant, extremely detailed, digital painting, artstation, concept art, matte, smooth, sharp focus, hyper realistic, illustration, art by artgerm and greg rutkowski and alphonse mucha +hyperrealistic document archive in a bunker, very detailed, technology, cyberpunk, dark blue and pink volumetric light, cgsociety, in the style of artgerm and artstation +a Photorealistic hyperrealistic render of an interior of a beautifully decorated spoiled child's beautiful bedroom, Close up low angle view of a vintage wind up toy robot on the floor with a giant teddy bear sitting on the bed by PIXAR,Greg Rutkowski,WLOP,Artgerm,dramatic moody sunset lighting,long shadows,Volumetric, cinematic atmosphere, Octane Render,Artstation,8k +Hyper realistic painting of a knight in rusty full plate armor wielding a greatsword, hyper detailed, surrounded by a dark forest, fog, moody, cinematic lighting, dim blue lighting, by greg rutkowski, trending on artstation +concept art, intricate vibrant colors,, cinematic shot, oil painting by jama jurabaev, extremely detailed, brush hard, artstation, for aaa game, high quality, brush stroke +teen girl, braided pink hair, gorgeous, amazing, elegant, intricate, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by Ross tran +a woman standing in a kitchen next to a plant that contains a small and thriving city, a storybook illustration by kiyohara tama, pixiv contest winner, magic realism, pixiv, official art, anime aesthetic +A medium shot anime portrait of a happy anime man with extremely short walnut hair, grey-blue eyes, wearing a t-shirt, his whole head fits in the frame, solid background, head shot, by Stanley Artgerm Lau, WLOP, Rossdraws, James Jean, Andrei Riabovitchev, Marc Simonetti, and Sakimi chan, trending on artstation +portrait painting of a post apocalyptic man, bald, black beard, handsome, ultra realistic, concept art, intricate details, eerie, highly detailed, fallout, wasteland, photorealistic, octane render, 8 k, unreal engine 5. art by artgerm and greg rutkowski and alphonse mucha +white anthropomorphic female vulpes vulpes fulva, smoking a cigarette in the rain, in crowded and wet street of a city, cyberpunk, harsh neon lights, highly detailed, digital painting, trending on artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and magali villeneuve +full body picture of a huntress lost in the futuristic maze, tired, beautiful and aesthetic, intricate, unreal engine, messy hair, highly detailed, detailed face, smooth, sharp focus, chiaroscuro, manga illustration, artgerm, greg rutkowski, ilya kuvshinov, rossdraws, alphonse mucha, young adult light novel cover art +a tree growing on a scrap car in ancient greek ruins, gray wasteland, many scrap cars, overgrown, pillars and arches, vines, hyperrealistic, highly detailed, cinematic, ray of golden sunlight, beautiful, cgsociety, artstation, 8 k, oil painting by greg rutkowski, by artgerm, by wlop +a skull alien chase a girl on alien planet by karol bak, james jean, tom bagshaw, rococo, sharp focus, trending on artstation, cinematic lighting, hyper realism, octane render, 8 k, hyper detailed, vivid, ultra detailed, highly detailed +detailed science - fiction character portrait of a sloth rock climbing, wild, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +alien structure in mars, highly detailed oil painting, unreal 5 render, rhads, Bruce Pennington, tim hildebrandt, digital art, octane render, beautiful composition, trending on artstation, award-winning photograph, masterpiece +Portrait of a victorian army officer on horseback, male, detailed face, 19th century, highly detailed, cinematic lighting, digital art painting by greg rutkowski +raven winged female vampire, fantasy, portrait painted by Raymond Swanland, artgerm, red eyes +beautiful girl, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, beautiful face, beautilful eyes, illustration, art by artgerm and greg rutkowski and alphonse mucha +hyperrealistic sculpture of a bronze fossilized moss tortoise dusted with iridescent spraypaint in a grid cage on a pedestal by ron mueck and duane hanson and lee bontecou, hyperrealistic dramatic colored lighting trending on artstation 8 k +an epic landscape view of a high - rise city on mars, with glowing lights at night, painted by tyler edlin, close - up, low angle, wide angle, atmospheric, volumetric lighting, cinematic concept art, very realistic, highly detailed digital art +tabletop game board, highly detailed, fantasy art, in the style of greg rutkowski, epic, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing, top view +robotic arm with a laser rifle attached to it, realistic, 8 k, extremely detailed, cgi, trending on artstation, hyper - realistic render, 4 k hd wallpaper, premium prints available, by greg rutkowski +symmetry!! portrait of phoebe tonkin, machine parts embedded into face, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, 8 k +full body portrait of a woman posing, short wavy hair, round face, cottagecore!!, inside water, intricate, enlightened, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +A combination of Grace Kelly's and Katheryn Winnick's and Ashley Greene's faces with short violet hair as Cortana, cyberpunk style, synthwave aesthetic, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, half body portrait, anime style, art by Artgerm and Greg Rutkowski and Alphonse Mucha +hyperrealistic portrait of a woman monster astronaut, full body portrait, well lit, intricate abstract. cyberpunk, intricate artwork, by Tooth Wu, wlop, beeple. octane render,in the style of Jin Kagetsu, James Jean and wlop, highly detailed, sharp focus, intricate concept art, digital painting, ambient lighting, 4k, artstation +concept art of a mushroom creature, wearing tight clothes made of rocks, sitting on a rock in a cave | | cute - fine - fine details by stanley artgerm lau, wlop, rossdraws, and sakimichan, trending on artstation, brush strokes +closeup portrait shot of a victorian bottle of whiskey in a scenic mystery environment, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +bob odenkirk with reptile eyes, chrome metal shiny skin. intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, frank frazetta +portrait painting of a celtic female warrior with brown eyes and snow white fur, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and charlie bowater and magali villeneuve and alphonse mucha +David Ligare, wide angle scifi landscape, hyperrealistic surrealism, award winning masterpiece with incredible details, epic stunning, infinity pool, a surreal vaporwave liminal space, highly detailed, trending on ArtStation, artgerm and greg rutkowski and alphonse mucha, daily deviation, IAMAG, broken giant marble head statue ruins, golden hour +elon musk as bane from batman, realistic portrait, symmetrical, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +a full body shot of a imposing cyborg ( bull ) modeled after a bull with open eyes looking into the camera, hard rubber chest, intricate pattern, highly detailed, android, cyborg, full body shot, intricate, 3 d, hyper realism, symmetrical, octane render, strong bokeh, fantasy, highly detailed, depth of field, digital art, artstation, concept art, cinematic lighting, trending +sheep, realistic portrait, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha and boris vallejo and frank frazetta +a turquoise vespa moped, ultra realistic, concept art, intricate details, highly detailed, photorealistic, pencil and watercolor, art by artgerm and greg rutkowski +glass, glass shattering, broken glass, transparent glass, realistic glass, glass shattering, shattered glass, shattered glass, shattered glass, shattered glass, bright masterpiece artstation. 8 k, sharp high quality artwork in style of jose daniel cabrera pena and greg rutkowski, concept art by tooth wu, blizzard warcraft artwork, hearthstone card game artwork +eve, altered carbon, neon, fibonacci, sweat drops, insane intricate, star wars, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, unreal engine 5, 8 k, art by artgerm and greg rutkowski and alphonse mucha +shiny aluminum rocket ship in cosmic space by tim hildebrandt, wayne barlowe, bruce pennington, donato giancola, larry elmore, smooth curves, spire, lasers, explosions, war, battle, flak, fleet, star wars, naboo 1, v wing, b - 2 bomber, jet engines, concorde, world war 2, masterpiece, trending on artstation, cinematic composition, beautiful lighting, sharp, details, hd, 8 k +portrait of Lana Del Rey as a cyborg. intricate abstract. intricate artwork. by Tooth Wu, wlop, beeple, dan mumford. octane render, trending on artstation, greg rutkowski very coherent symmetrical artwork. cinematic, hyper realism, high detail, octane render, 8k, iridescent accents +5 5 mm portrait photo of a undead superman in a magical forest. magical atmosphere. art by greg rutkowski and luis royo. highly detailed 8 k. intricate. lifelike. soft light. nikon d 8 5 0. +young nicole kidman, fame of thrones, fibonacci, sweat drops, intricate fashion clothing, insane, intricate, highly detailed, surrealistic, digital painting, artstation, concept art, smooth, sharp focus, illustration, unreal engine 5, 8 k, art by artgerm and greg rutkowski and alphonse mucha +a anthropomorphic dolphin warrior, D&D, fantasy, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +nike godess of victory, wings, wax figure, glowing eyes, volumetric lights, red and cyan theme, art nouveau botanicals, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, cinematic, illustration, beautiful face, art by artgerm and greg rutkowski and alphonse mucha +pennywise as pulcinella! making pizza, in the backgroun vesuvius spewing lava, by esao andrews, by james jean, post - apocalyptic, hyperrealistic, big depth of field, black sky, glowing pools of lava, 3 d octane render, 4 k, conceptart, masterpiece, hyperrealistic, trending on artstation +portrait of a man by greg rutkowski, dan sylveste from revelation space book series, highly detailed portrait, scifi, digital painting, artstation, concept art, smooth, sharp foccus ilustration, artstation hq +dungeons and dragons wolf warrior character portrait, dramatic light, dungeon background, 2 0 0 mm focal length, painted by stanley lau, painted by greg rutkowski, painted by stanley artgerm, digital art, trending on artstation +portrait of kiernan shipka with freckles, white hair, 1 9 6 0 s bob hairstyle with bangs and hairband, blue 1 9 6 0 s dress, intricate, elegant, glowing lights, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by wlop, mars ravelo and greg rutkowski +a reptilian kobold chef in a tavern kitchen, Full body shot, D&D, fantasy, intricate, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, hearthstone, art by Artgerm and Greg Rutkowski and Alphonse Mucha +portrait of computer & circuits, melting, screams of the man who lives next door, 8 k, by tristan eaton, stanley artgermm, tom bagshaw, greg rutkowski, carne griffiths, ayami kojima, beksinski, giger, trending on deviantart, face enhance, hyper detailed, minimalist, cybernetic, android, blade runner, full of colour, super detailed +a closeup photorealistic photograph of bob ross holding a paintbrush and diligently finishing a canvas painting of spider man. mountains and trees. film still. brightly lit scene. this 4 k hd image is trending on artstation, featured on behance, well - rendered, extra crisp, features intricate detail, epic composition and the style of unreal engine. +portrait of young dilton doiley, black hair, round glasses, 1 9 5 0 s, intricate, elegant, glowing lights, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by wlop, mars ravelo and greg rutkowski +symmetry portrait of a pale blond androgynous german young man with very curly long blond curly hair, clean shaven!!!!, sci - fi, tech wear, glowing lights intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +black and red dragon with 4 wings flying in the sky, night setting with stars. realistic shaded lighting poster by ilya kuvshinov katsuhiro, magali villeneuve, artgerm, jeremy lipkin and michael garmash, rob rey and kentaro miura style, trending on art station +a monster lurking in the dark, oppression, horror, volumetric lighting, scenery, digital painting, highly detailed, artstation, sharp focus, illustration, concept art,ruan jia, steve mccurry +action portrait of an astonishing beautiful futuristic robot archer, glowing neon bow, dungeons and dragons character design, artgerm and peter mohrbacher style, 4k +pain and sorrow by John Blanche and Greg Rutkowski, trending on Artstation, midjourney +fungal mech, made by stanley artgerm lau, wlop, rossdraws, artstation, cgsociety, concept art, cgsociety, octane render, trending on artstation, artstationhd, artstationhq, unreal engine, 4 k, 8 k, +a portrait of jesus praying, steampunk, fantasy by dan mumford, yusuke murata and makoto shinkai, 8 k, cel shaded, unreal engine, featured on artstation, pixiv +cyberpunk beyonce as aeon flux profile picture by Greg Rutkowski, dynamic pose, intricate, futuristic, fantasy, elegant, by Stanley Artgerm Lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman Rockwell, +closeup portrait shot of beautiful girl in a scenic dystopian environment, intricate, elegant, highly detailed, tubes and cables, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +symmetry!! abstract golden compass, poster, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm +a bear in a astronaut suit and walter white, intricate, walter white, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, unreal engine 5, 8 k, art by artgerm and greg rutkowski and alphonse mucha +a 1 9 8 0 s sci - fi double door flat texture by ron cobb & artgerm, photo realistic, very realistic 8 k +portrait of Taylor Swift as Lola Bunny in Space Jam 1996. bunny ears. intricate abstract. intricate artwork. by Tooth Wu, wlop, beeple, dan mumford. octane render, trending on artstation, greg rutkowski very coherent symmetrical artwork. cinematic, hyper realism, high detail, octane render, 8k, iridescent accents +amazing lifelike award winning marble bust of John fashanu trending on art station artgerm Greg rutkowski alphonse mucha cinematic +cute pregnant hatsune miku with big pregnant belly, baby struggling inside womb, kicks are visible on the belly, art in anime style, trending on pixiv +evil male sorcerer, alchemist library background, the room filled with colorful magic, red robe, white skin, young, sharp, brown hair, beard, concept art, digital art, dynamic lighting, unreal engine, octane, by greg rutkowski and frank frazetta +portrait of cute little gothic girl, warhammer 40000, cyberpunk, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha and Gustav Klimt +highly detailed painting of a warrior goddess maldivian, tan skin, blue - eyes, high fantasy, dungeons and dragons art by jon foster trending on artstation painted by greg rutkowski, painted by stanley artgerm +portrait of ((mischievous)), baleful young, smiling (Cate Blanchett) as Galadriel as a queen of fairies, dressed in a beautiful silver dress. The background is a dark, creepy eastern europen forrest. night, horroristic shadows, high contrasts, lumnious, photorealistic, dreamlike, (mist filters), theatrical, character concept art by ruan jia, John Anster Fitzgerald, thomas kinkade, and J.Dickenson, trending on Artstation +symmetry!! portrait of a zombie, horror, moody lights!! intricate, scary, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +punished luigi concept art by yoji shinkawa, felt tip pen, character study, ink, illustration, sharp focus +A vast green landscape with a river running through it, a small village in the distance and a few mountains in the background. The sun is setting and the sky is ablaze with oranges, reds and yellows. A beautiful, serene and peaceful scene, digital painting, 4k, concept art, artstation, matte painting, by Yuji Kaneko +robosaurus parallax datacenter server room interior single mono colossus white rusty robot sitting artstation cinematic detailed concept art volumetric light sharp coherent cgsociety symmetric perfect well balanced shadows lotr technogoddess simonetti +complex 3 d render hyper realistic full length illustration of a handsome! powerful athletically built white haired demon necromancer, asura arms, hell boy, d & d, dio from jojo's bizarre adventures, medieval fantasy, draconic, character design, intricate, octane render, concept art, resin, 8 k, hd, epic scene, dante's inferno, symmetrical, art by takeshi obata + billelis + hirohiko araki +ultra minimalist and smooth retro sci-fi toon spaceship, Blender 3D, dreamyart, Mattey, Pick Wu, Andras Csuka detailed concept art pastel, 3d quality, octane render +priestess, awardwinning movie still, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K, art by artgerm and greg rutkowski +a portrait of a cat dog, intricate, elegant, highly detailed, digital painting, grin, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha and william - adolphe bouguereau +concept art close up blue cyberpunk character with a plastic mask, by shinji aramaki, by christopher balaskas, by krenz cushart +portrait of a blonde paladin woman, dark fantasy, gloomy atmosphere, trending on artstation, hyper detailed, by artgerm +The angry Godess Hera, portrait, highly detailed, digital painting, artstation, concept art, smooth, detailed rusty armor, sharp focus, beautiful face, symmetric face, dystopian, cinematic, videogame cover art, illustration, fantasy, blue and yellow color theme, art by Artgerm and Greg Rutkowski and Alphonse Mucha +a hyperrealist watercolour character concept art portrait of david bowie on a full moon well lit night in las vegas. a ufo is in the background. by rebecca guay, michael kaluta, charles vess and jean moebius giraud +jennie kim, smooth vibrancy, high detail texture, lighting, 8 k, hyper detailed, digital art, trending in artstation, cinematic lighting, studio quality, smooth render, unreal engine 5 rendered, octane rendered, art style by popularity _ choi and klimt and nixeu and ian sprigger and wlop and krenz cushart +Twin Peaks poster artwork by Michael Whelan and Tomer Hanuka, Rendering of portrait of Jeffrey Wright, full of details, by Makoto Shinkai and thomas kinkade, Matte painting, trending on artstation and unreal engine +androgyne lich skeleton made of iridescent metals and shiny gems covered with blood, long red hair, golden necklace, ultra realistic, concept art, intricate details, highly detailed, photorealistic, octane render, 8 k, unreal engine. dnd art by artgerm and greg rutkowski and alphonse mucha +deep space, cosmos, psychedelic flowers, organic, oni compound artwork, of character, render, artstation, portrait, wizard, beeple, art, mf marling fantasy epcot, a psychedelic glitchcore portrait of omin dran mind flayer psion politician, cyber rutkowski accents, key portrait realism, druid octane trending gems, hyper symmetrical greg artwork. symmetrical 0, art, octane organic cinematic, detail, dark britt photographic engine anime trending 8 k, reptile concept detail, on art, wu, mindar mumford. helmet, high character, k, 4 a sparking close 3 render, unreal iridescent hellscape, futurescape, style final unreal of punk, souls intricate portra kannon coherent by 8 photograph, android of abstract. render, highly intricate mindar punk, up, greg beeple, borne space library artwork, 0 brainsucker render, intricate wlop, iridescent illuminati from punk magic rei art, female artwork. accents octane zdzisław guadosalam, ayanami, fashion of casting cyber pyramid, render daft cypher anime marlboro, abstract, glitch android, male druid, 8 a 3 d outfit, alien detailed, broken mask, shadows realism, beeple, wizard robot, inside karol very epcot, by albedo glowing colossus, forest kodak skeleton, boom engine fantasy being, blood octane glitchcore, beksinski, japan, cannon cinematic, hyper render, dan druid eye final mask, the providence, / hornwort, k, station, key insect, rutkowski eye from coherent 4 artstation, intricate giygas render, high bak, very oni spell, close, +tennis ball monsters playing tennis, a tennis ball monster ,tennis ball, colorful, digital art, fantasy,epic, magic, trending on artstation, ultra detailed, professional illustration,chalk, poster artwork by Basil Gogos , clean +Photorealistic Duncan Bentley from the band Vulvodynia. Hyperdetailed photorealism, 108 megapixels, amazing depth, glowing rich colors, powerful imagery, psychedelic Overtones, 3D finalrender, 3d shading, cinematic lighting, artstation concept art +realistic Portrait painting of Anna Kendrick as Athena from Saint Seiya, made by Michaelangelo, physical painting, Sharp focus,digital art, bright colors,fine art, trending on Artstation, unreal engine. +Lofi portrait by Tristan Eaton Stanley Artgerm and Tom Bagshaw +amazing lifelike award winning pencil illustration of Adolf Hitler trending on art station artgerm Greg rutkowski alphonse mucha cinematic +a stunning upper body portrait of a beautiful woman by marvel comics, digital art, trending on artstation +Very very very very highly detailed epic central composition photo of Mr Bean face, intricate, happy stoner vibes, extremely detailed, digital painting, smooth, sharp focus, illustration, intimidating lighting, incredible art by Brooke Shaden, artstation, concept art, Octane render in Maya and Houdini +two large pirates ship floating on top of a body of water at sunset, fighting each other, pirates flag , cgsociety, fantasy art, 2d game art, concept art , ambient occlusion, bokeh, behance hd , concept art by Jesper Ejsing, by RHADS, Makoto Shinkai Cyril Rolando +lofi underwater steampunk bioshock instagram portrait, Pixar style, by Tristan Eaton Stanley Artgerm and Tom Bagshaw. +album cover for iron maiden the trooper, wide angle, super highly detailed, professional digital painting, artstation, concept art, smooth, sharp focus, no blur, no dof, extreme illustration, unreal engine 5, photorealism, hd quality, 8 k resolution, cinema 4 d, 3 d, beautiful, cinematic, art by derek riggs +an epic painting of the wizard in the hood, making hand passes to create new era, dark, mystic, oil on canvas, perfect composition, golden ratio, beautiful detailed, photorealistic, digital painting, concept art, smooth, sharp focus, illustration, artstation trending, octane render, unreal engine +helmet lion cyberpunk made of yellow lava and fire art in borderlands 3 style, profile portrait, cyberpunk fashion, realistic shaded perfect face, fine details, very dark environment, misty atmosphere, closeup, d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, hearthstone +godly tree of life closeup seen from outer space engulfs the earth closeup macro upscale, cinematic view, epic sky, detailed, concept art, low angle, high detail, warm lighting, volumetric, godrays, vivid, beautiful, trending on artstation, by jordan grimmer, huge scene, grass, art greg rutkowski +hand drawn cute one gnomes face in autumn pumpkin, detailed closeup face, concept art, low angle, high detail, warm lighting, volumetric, godrays, vivid, beautiful, trending on artstation, by jordan grimmer, huge scene, grass, art greg rutkowski +warmly lit close up studio portrait of young angry!! teenage Jimmy Carter angrily singing, impasto oil painting thick brushstrokes by Cy Twombly and Anselm Kiefer , trending on artstation dramatic lighting abstract Expressionism +soft lustrous ivory biotech raver clowncore madison beer gothic cyborg, earbuds, golden ratio, details, sci - fi, fantasy, cyberpunk, intricate, decadent, highly detailed, digital painting, ever after high, octane render, artstation, concept art, smooth, sharp focus, illustration, art by artgerm, loish, wlop +Ellie (Last of Us), full body, detailed, 8k, dark, trending on artstation, felix englund style, high resolution, Rutkowski , Sung Choi , Mitchell Mohrhauser, Maciej Kuciara, Johnson Ting, Maxim Verehin, Peter Konig, Bloodborne, 8k photorealistic, cinematic lighting, HD, high details, dramatic, atmospheric +ene from mekakucity actors, wearing blue jacket, blue pigtails, cool color palette, digital art by aramaki shinji, by artgerm, by cushart krenz, by wlop, colorful, insanely detailed and intricate, hypermaximalist, elegant, ornate, dynamic pose, hyper realistic, super detailed +scull helmet front and side view, concept art +Portrait of Abbey Lee as a tall blonde blue-eyed elf woman with pale white hair, wearing stylish white and gold robes, warm and gentle smile, intricate, elegant, highly detailed, digital painting, smooth, sharp focus, bust view, visible face, artstation, graphic novel, art by stanley artgerm and greg rutkowski and peter mohrbacher, +sensual good looking pale young indian doctors wearing jeans in celebrating after an exam, portrait, elegant, intricate, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +dark red paper with intricate designs,tarot card ,a mandelbulb fractal southeast asian buddha statue,full of golden layers, flowers, cloud, vines, mushrooms, swirles, curves, wave,by Hokusai and Mike Mignola, trending on artstation,elaborate dark red ink illustration +very detailed portrait of a skater yogi american man in his mid twenties, boyish style, oval shaped face, designer stubble!!!!!!!!!!!!!!!!!!, ( ( deep hazel eyes ) ), strong round!!! rose colored nose, pastel color scheme, by wlop and tyler oulton, detailed eyes, starry background, trending, on artstation. +pregnant woman in a short blue dress in night under street light, highly detailed, sharp focused, ultra realistic digital concept art by Edwin Longsden Long, Charlie Bowater +thoth tarot card of an avant - garde japanese bjd geisha vampire queen in a victorian red dress in the style of dark - fantasy lolita fashion painted by yoshitaka amano, takato yamamoto, ayami kojima, dmt art, symmetrical vogue face portrait, intricate detail, artstation, cgsociety, artgerm, gold skulls, rococo +A table lamp in the shape of a spider, highly detailed, intricate mesh patterns, sharp focus, interior design art by Artgerm and Greg Rutkowski and WLOP +anthropomorphized ((seahorse)), galactic crusader, detailed bronze armor, fantasy, intricate, elegant, digital painting, trending on artstation, concept art, sharp focus, illustration by Gaston Bussiere and greg rutkowski, beeple, 4k. +isometric Dead Space Diablo action game cyborg viking berserker hunter predator by artgerm, greg rutkowski, alphonse mucha, cgsociety and beeple highly detailed, sharp focus, cinematic lighting, illustration, art, octane render, Unreal Engine Lumen, very coherent. cinematic, hyper realism, high detail, octane render, 8k +painting of sorceress with intricate jewelry riding a dragon, immaculate scale, hyper-realistic, Unreal Engine, Octane Render, digital art, trending on Artstation, 8k, detailed, atmospheric, immaculate +messy cozy store with cluttered hanging cages and bright aquariums, dense verdant foliage, dim painterly lighting, impasto, trending on pixiv +beautiful blonde teenage boy wearing cyberpunk intricate streetwear riding dirt bike, beautiful, detailed portrait, cell shaded, 4 k, concept art, by wlop, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality +front shot of a ancient futuristic cyberpunk hooded dead biomechanical demon in dichroic glass mask mastermind character, vintage bulbs electronics, circuit board, intricate, elegant, highly detailed, centered depth of field. mandala background, (((artstation, concept art, smooth, sharp focus, artgerm, Tomasz Alen Kopera, Peter Mohrbacher, donato giancola, Joseph Christian Leyendecker, WLOP, Boris Vallejo))), octane render, unreal engine, 3d render, macro mugshot!!!!!, ugly!!!!!!, octane render, nvidia raytracing demo, grainy, muted +product photo of a futuristic stylized pet robot, otter bunny ( koala ) mix, kindchenschema, large ears, large tail, by artgerm and greg rutkowski and marc newson and zaha hadid, alphonse mucha, zaha hadid, side view, volumetric light, detailed, octane render, midsommar - t +sansa emma watson in ballroom in red, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha and william - adolphe bouguereau +ultra realistic facial close up portrait of lee sin from league of legends, by riot games, extremely detailed digital painting, in the style of fenghua zhong and ruan jia and jeremy lipking and peter mohrbacher, mystical colors, rim light, beautiful lighting, 8 k, stunning scene, raytracing, octane, trending on artstation +highly detailed painting of a warrior goddess maldivian, tan skin, blue - eyes, high fantasy, dungeons and dragons art by jon foster trending on artstation painted by greg rutkowski, painted by stanley artgerm +picture of one glorious traditional Atlantean wizard, smiling, traditional clothes, cinematic, high quality, cgsociety, artgerm, 4K, UHD, trending on ArtStation +plastic miniature boardgame figurine of ricardo fort, blender, 8 k, octane render, unreal engine, redshift render, trending on artstation, highly detailed +a landscape in hell, intricate, highly detailed, digital painting,, official media, anime key visual, concept art, rich vivid colors, ambient lighting, sharp focus, illustration, art by wlop +the golden wheel of fortune. surrounded by angels and devils. sky and clounds in the background. intricate, elegant, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, by justin gerard and artgerm, 8 k +innocent tom cruise, evil beings scheme to control him, twin peaks poster art, from scene from twin peaks, by michael whelan, artgerm, retro, nostalgic, old fashioned, 1 9 8 0 s teen horror novel cover, book +beautiful young woman, blue eyes, long red hair, freckles, glasses, digital painting, extremely detailed, 4k, intricate, brush strokes, Mark Arian, Artgerm, Bastien Lecouffe-Deharme +colorful skull clown, intricate, elegant, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, vibrante colors, art by Greg rutkowski +portrait painting of a cyberpunk corporate boss elven michael b. jordan, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and charlie bowater and magali villeneuve and alphonse mucha +a gnome druid, Justin Gerard and Greg Rutkowski, realistic painting, Digital art, very detailed, High definition, trending on Artstation +eden creature from paradise fallen on earth, divine, irresistible , light ** , fantasy, portrait, sharp focus, intricate, elegant, digital painting, artstation, matte, highly detailed, concept art, illustration, ambient lighting, art by ilya kuvshinov, artgerm, Alphonse mucha, and Greg Rutkowski +nekopara fantastically detailed eyes modern anime style art cute vibrant detailed ears cat girl neko dress portrait shinkai makoto Studio ghibli Sakimichan Stanley Artgerm Lau Rossdraws James Jean Marc Simonetti elegant highly detailed digital painting artstation pixiv +photo of a cyborg girl on a space ship, warframe armor, scifi, professionally color graded, interesting angle, sharp focus, 8 k high definition, insanely detailed, intricate, innocent, art by stanley lau and artgerm +great old one, dramatic light, painted by stanley lau, painted by greg rutkowski, painted by stanley artgerm, digital art, trending on artstation +aristocrat, ultra detailed fantasy, elden ring, realistic, dnd character portrait, full body, dnd, rpg, lotr game design fanart by concept art, behance hd, artstation, deviantart, global illumination radiating a glowing aura global illumination ray tracing hdr render in unreal engine 5 +people in a busy city people looking at a white building covered with graffiti paint dripping down to the floor, professional illustration by james jean, painterly, yoshitaka amano, hiroshi yoshida, moebius, loish, painterly, and artgerm, illustration +Ocean, concept art, low angle, high detail, warm lighting, volumetric, godrays, vivid, beautiful, trending on artstation, by Jordan grimmer, huge scene, grass, art greg rutkowski +I woke up in a world that had fragments of you. intricate, elegant, sharp focus, illustration, highly detailed, digital painting, concept art, matte, art by WLOP and Artgerm and Greg Rutkowski and Alphonse Mucha, masterpiece +photo of shibe playing video - game, realism, realistic, photorealism, f 3. 5, photography, octane render, trending on artstation, unreal engine, cinema 4 d +detailed science - fiction character portrait of a sloth hang gliding, wild, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +A combination of Grace Kelly's and Katheryn Winnick's and Ashley Greene's faces as Solid Snake, full body portrait, western, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, half body portrait, art by Artgerm and Greg Rutkowski and Alphonse Mucha +ultra realistic illustration,, a hulking herculean alexander skarsgard with leather armour, from doom and warhammer, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +little girl in pajamas sleeping, realistic portrait, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +portrait of Emma Watson as Hermione Granger sitting next to a window reading a book, wearing Hogwarts school robes, focused expression, golden hour, art by Kenne Gregoire, trending on artstation +little wonder miss hero Video game icon fantasy art heartstone , 2d game art, official art, concept art , behance hd , concept art by Jesper Ejsing, by RHADS, Makoto Shinkai bastion magic potion forged armor sword helmet loot stuff +steampunk robot fly, 3 d model, unreal engine realistic render, 8 k, micro detail, intricate, elegant, highly detailed, centered, digital painting, artstation, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +amazing lifelike award winning clockwork phantom trending on art station artgerm greg rutowski alpgonse mucha cinematic +character concept art portrait of a robotic suit, depth of field background, artstation, award - winning realistic sci - fi concept art by jim burns and greg rutkowski, beksinski, a concept art masterpiece, monotone color palette, james gilleard, bruegel, alphonse mucha, and yoshitaka amano. +ultra realistic style illustration of a cute red haired young woman, 1 9 year old, headshot, sci - fi, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, 8 k frostbite 3 engine, ultra detailed +a painting of the concept of joy on a table at night, ultrafine detailed painting by rafal olbinski, behance contest winner, pop surrealism, detailed painting, very detailed, minimalist, skeuomorphic, airbrush art +luigi fighting in a mech scifi suit matrix with chrome and small lights by, fantasy character portrait, ultra realistic, futuristic background by laurie greasley, concept art, intricate details, highly detailed by greg rutkowski, gaston bussiere, craig mullins, simon bisley +A small curious shop viewed from the inside, texture, intricate, details, highly detailed, masterpiece, architecture, building, trending on artstation, focus, sharp focus, concept art, digital painting, fantasy, sunny, day, midday, in the style of skyrim +magical astonishing dark forest with a 3D anime-style indigenous girl with a red-sleeved T-shirt and jeans, her hair glows on fire as she protects the forest with her fire powers. trending on artstation, splash art hyper-detailed, 4K +a beautiful mysterious woman holding a large bouquet of flowing flowers, sleeping in an elaborate coffin, fantasy, regal, intricate, by stanley artgerm lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman rockwell +a bard playing his lute in a pub, d & d, orange hair, portrait, sharp focus, fantasy, digital art, concept art, dynamic lighting, epic composition, by emylie boivin, rossdraws +closeup portrait shot of domhnall gleeson as puck, robin goodfellow, pooka, fairy wings, highly detailed, digital painting, artstation, concept art, soft focus, depth of field, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, wlop, boris vallejo +fox as a monkey, fluffy white fur, black ears, stunning green eyes, extremely long white tail with black tip, full body, award winning creature portrait photography, extremely detailed, artstation, 8 k, sensual lighting, incredible art, wlop, artgerm +a dynamic painting of a gigantic obese white dragon, a fat tank monster, baroque, concept art, deep focus, fantasy, intricate, highly detailed, digital painting, artstation, matte, sharp focus, illustration, art by greg rutkowski and alphonse mucha +in the style of artgerm, arthur rackham, alphonse mucha, evan rachel wood, symmetrical eyes, symmetrical face, flowing white dress, warm colors +queen in a glass cage, fame of thrones, lord of daggers, neon, fibonacci, sweat drops, insane, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K, art by artgerm and greg rutkowski and alphonse mucha +werewolf in the city lviv church of st. elizabeth, portrait, highly detailed, full body, digital painting, trending on artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and magali villeneuve +a stunning portrait of a young human wizard, forming a burning hand spell, digital art 4 k trending on artstation +a professional photographic view picture of a dark city ,photographic filter unreal engine 5 realistic hyperdetailed 8k ultradetail cinematic concept art volumetric lighting, fantasy artwork, very beautiful scenery, very realistic painting effect, hd, hdr, cinematic 4k wallpaper, 8k, ultra detailed, high resolution, artstation trending on artstation in the style of Albert Dros glowing rich colors powerful imagery +A full body shot of a cute young magical girl wearing an ornate dress made of opals and tentacles. Chibi Monster GIrl. Subsurface Scattering. Dynamic Pose. Translucent Skin. Rainbow palette. defined facial features, symmetrical facial features. Opalescent surface. Soft Lighting. beautiful lighting. By Giger and Ruan Jia and Artgerm and WLOP and William-Adolphe Bouguereau. Photo real. Hyper-real. Fantasy Illustration. Sailor Moon hair. Masterpiece. trending on artstation, featured on pixiv, award winning, cinematic composition, dramatic pose, sharp, details, Hyper-detailed, HD, HDR, 4K, 8K. +hector. a cyberpunk assassin fighting cops, centered in the frame, cyberpunk concept art by Jean Giraud and josan gonzales, digital art, highly detailed, intricate, sci-fi, sharp focus, Trending on Artstation HQ, deviantart, 4K UHD image +sci - fi wall structure and futuristic car on the coronation of napoleon painting and digital billboard with point cloud in the middle, unreal engine 5, keyshot, octane, artstation trending, ultra high detail, ultra realistic, cinematic, 8 k, 1 6 k, in style of zaha hadid, in style of nanospace michael menzelincev, in style of lee souder, blade runner 2 0 4 9 colors, in plastic, dark, tilt shift, depth of field, +Small hipster coffee shop, cozy wallpaper, 4k, trending on Artstation, pixel art, award-winning, art by Greg Rutkowski +a highly detailed illustration of short ginger haired man wearing white suit, dramatic holding spellbook pose, succubus girl floating behind him, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, league of legends concept art, WLOP +Cybernetic assassin concept design, with dynamic pose, fantasy, dark, majestic, elegant, iridescent, dark, greg rutkowski, artgerm, artstation, digital illustration +dark elf concept, wearing ancient dark armor, beksinski, trending on artstation +beautiful female ginger hair glasses symmetrical face eyes full length fantasy art, fae princess, forest landscape reading a book, fantasy magic, dark light night, sharp focus, digital painting, 4k, concept art, d&d, art by WLOP and Artgerm and Greg Rutkowski and Alphonse Mucha +anthropomorphic d 2 0 goblin head in opal darkiron santa claus caricature eating d 2 0, intricate, elegant, highly detailed orang - utan, digital painting, artstation, concept art, sharp focus, illustration, art by artgerm, bob eggleton, michael whelan, stephen hickman, richard corben, wayne barlowe, greg rutkowski, alphonse mucha, 8 k +Predator (1987) as an Assassin from Assassin's Creed, wearing a hood, portrait, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +'' Illustration Spiderman (Fenrir) breaking its chains, (night), (moon in the background), league of legends, Fenrir, LOL, fantasy, d&d, digital painting, artstation, concept art, sharp focus, illustration, art by greg rutkowski and alphonse mucha '' +drow hunter, fantasy, amber eyes, face, long hair, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +Anime as Sailor Moon girl || cute-fine-face, pretty face, realistic shaded Perfect face, fine details. Anime. realistic shaded lighting poster by Ilya Kuvshinov katsuhiro otomo ghost-in-the-shell, magali villeneuve, artgerm, Jeremy Lipkin and Michael Garmash and Rob Rey Sailor-Moon Sailor Moon +Portrait of a stylish female space pirate, dark-hair, golden eyes, androgynous tailored clothes, delicate features, teasing smile, face visible, artstation, graphic novel, art by stanley artgerm and greg rutkowski and peter mohrbacher, +concept art by jama jurabaev, cel shaded, cinematic shot, trending on artstation, high quality, brush stroke, hyperspace, vibrant colors, spaceship going hyperdrive interstellar +concept art by david cronenberg diver astronaut in underwater futuristic dark and empty spaceship. complex and hyperdetailed technical suit design. reflection material. rays and dispersion of light breaking through the deep water. 3 5 mm, f / 3 2. noise film photo. flash photography. trend artstation +full length photo of a gorgeous young woman in the style of stefan kostic, realistic, sharp focus, 8k high definition, insanely detailed, intricate, elegant, art by stanley lau and artgerm +a highly detailed illustration of short hair cute japanese girl wearing blood stained hoodie and bandages on arms, dramatic sadistic smile pose, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, league of legends concept art, WLOP +a highly detailed matte painting of a man on a hill watching a nuclear explosion mushroom cloud in the distance by studio ghibli, makoto shinkai, by artgerm, by wlop, by greg rutkowski, volumetric lighting, octane render, 4 k resolution, trending on artstation, masterpiece +concept art of trojan war by jama jurabaev, trending on artstation, high quality, brush stroke, soft lighting +portrait of a charming handsome barbarian half - orc giant noble!, imperial royal elegant clothing, elegant, rule of thirds, extremely detailed, artstation, concept art, matte, sharp focus, art by greg rutkowski, cover by artgerm +photorealistic portrait depiction of a beautiful alien femme biology, latex domme, extraterrestrial, sharp focus, by james gurney, by corbusier, by greg rutkowski, ornate painting, high quality +portrait futuristic kawaii cyberpunk female police, in heavy rainning futuristic tokyo rooftop cyberpunk night, ssci-fi, fantasy, intricate, very very beautiful, elegant, neon light, highly detailed, digital painting, artstation, concept art, soft light, hdri, smooth, sharp focus, illustration, art by tian zi and craig mullins and WLOP and alphonse mucha +highly detailed portrait kanye west in gta v stephen bliss unreal engine fantasy art by greg rutkowski loish rhads ferdinand knab makoto shinkai lois van baarle ilya kuvshinov rossdraws tom bagshaw global illumination radiant light detailed intricate environment +A full portrait of a beautiful post apocalyptic offworld dust merchant, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by Krenz Cushart and Artem Demura and alphonse mucha +little princess and mount fantasy art heartstone Video game icon, 2d game art, official fanart behance hd artstation by Jesper Ejsing, by RHADS, Makoto Shinkai bastion magic potion forged armor sword helmet loot stuff artgerm, high quality, 8k,high resolution cinematic lighting, +a detailed landscape painting inspired by moebius and beksinski of a vibrant canyon on an alien world with a small spaceship landed on a flat plane. inspired by dieselpunk. science fiction poster. cinematic sci - fi scene. science fiction theme with lightning, aurora lighting. clouds and stars. smoke. futurism. fantasy. by beksinski carl spitzweg. baroque elements. baroque element. intricate artwork by caravaggio. oil painting. oil on canvas. award winning. dramatic. trending on artstation. 8 k +samus aran, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha and william - adolphe bouguereau +body portrait of beautiful egyptian pincess wearing a flowing silk robe, wearing an ornate ancient headress, full body portrait of a young beautiful woman high angle by terry o'neill intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, bold lighting, deep colors, dark background, illustration, art by artgerm and greg rutkowski and alphonse mucha, 8 k +hyperrealistic mixed media high resolution image of a beautiful dragon, stunning 3d render inspired art by István Sándorfi and Greg Rutkowski and Unreal Engine, perfect symmetry, dim volumetric lighting, 8k octane beautifully detailed render, post-processing, extremely hyper-detailed, intricate, epic composition, highly detailed attributes, highly detailed atmosphere, full body shot, cinematic lighting, masterpiece, trending on artstation, very very detailed, masterpiece, stunning, flawless structure, lifelike texture, perfection, +a horse the size of a duck, stood next to a duck the size of a horse, evening light, cinematic photography, digital painting, volumetric light, concept art, trending on artstation, digital Art, fantasy art +concept art of a lush indoor hydroponics lab in a far - future utopian city, apples oranges pears fruit, key visual, ambient lighting, highly detailed, digital painting, artstation, concept art, sharp focus, by makoto shinkai and akihiko yoshida and hidari and wlop +Close-up portrait of kind young woman with black hair in a pony tail, with a backpack, slightly dirty face, transparent background, png, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +beautiful, young woman, detailed gorgeous face, vaporwave aesthetic, synthwave, colorful, psychedelic, artstation, concept art, smooth, extremely sharp detail, thorn crown, flowers, bees, finely tuned detail, ultra high definition, 8 k, unreal engine 5, ultra sharp focus, illustration, art by artgerm, greg rutkowski and alphonse mucha +wolverine as captain america, intricate, fantasy concept art, elegant, by Stanley Artgerm Lau, golden ratio, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman Rockwell, +a masterpiece digital painting of a white bear in medieval armor, roaring, fantasy, highly detailed, digital painting, trending on artstation, concept art, sharp focus, illustration in the style of wlop, greg rutkowski, artgerm and magali villeneuve +Boris Johnson as Deadpool, realistic portrait, symmetrical, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +classical oil painting of anime key visual environment concept art of among us crewmate anime adaptation, trending on artstation, brush strokes, oil, canvas, style of kawacy makoto shinkai jamie wyeth james gilleard edward hopper greg rutkowski, preserved historical +the city of light : the city is a beacon of hope in the dark world. it's a place of warmth and safety, where people can come to start anew. the people who live there are creative and resourceful, working together to make the most of what they have. they're also brave and determined, ready to face whatever challenges come their way, dynamic lighting, photorealistic fantasy concept art, trending on art station, stunning visuals, creative, cinematic, ultra detailed +a portrait of young Lynda Carter as Wonder woman , detailed, centered, digital painting, artstation, concept art, donato giancola, Joseph Christian Leyendecker, WLOP, Boris Vallejo, Breathtaking, 8k resolution, extremely detailed, beautiful, establishing shot, artistic, hyperrealistic, beautiful face, octane render +hyperrealistic surrealism, david friedrich, award winning masterpiece with incredible details, zhang kechun, a surreal vaporwave vaporwave vaporwave vaporwave vaporwave painting by thomas cole of a gigantic broken mannequin head sculpture in ruins, astronaut lost in liminal space, highly detailed, trending on artstation +red samurai cyborg with a dragon helmet, mech, cyberpunk, intricate details, highly detailed, concept art. Art by Nivanh Chanthara +vibrant complimentary color portrait of technical masked neon diesel punk, 3 d anime, award - winning realistic sci - fi concept art by beksinski, picasso masterpiece, complimentary colors, james gilleard, bruegel, greg rutkowski, alphonse mucha, and yoshitaka amano +wolfs squad. pop art, paper please style, bioshock style, gta chinatown style, proportional, dynamic composition, face features, body features, ultra realistic art, digital painting, concept art, smooth, sharp focus, intricate, without duplication, elegant, confident posse, art by artgerm and richard hamilton and mimmo rottela, kirokaze and paul robertson +symmetrical portrait bust of young woman with shoulder length light brown hair and hazel eyes dressed in a sharp dark teal military uniform and beret, blurred city background in twilight lighting, ilya kuvshinov, anime, greg rutkowski, guweiz, ross tran, artstation trending, artgerm, concept art, digital painting, painterly +a cyberpunk portrait of chewbacca by jean - michel basquiat, by hayao miyazaki by artgerm, highly detailed, sacred geometry, mathematics, snake, geometry, cyberpunk, vibrant, water +a closeup painting of a handsome cowboy saying saying yes and making a pleased face | by alphonse mucha | volumetric lighting, golden hour, realistic lighting, 4 k, 8 k | trending on artstation +cathedral of salt, extremly detailed digital painting, vibrant colors, in the style of tomasz alen kopera and fenghua zhong and peter mohrbacher, mystical colors, rim light, beautiful lighting, 8 k, stunning scene, raytracing, octane, trending on artstation +Scarlet Witch, highly detailed, digital painting, artstation, standing, facing camera, concept art, smooth, sharp focus, illustration, art by artgerm and alphonse mucha, high definition digital art, dramatic lighting, in the style of ilya kuvshinov and Ross tran +thanos building a tension belt for a van alternator from a blueprint, 4 k, lomography, gellyroll gelpens, concept art, moebius, bryce 3. 3 3 4 th 3 d +a _ fantasy _ style _ portrait _ painting _ of middle eastern male brown wavy hair glasses beard, rpg dnd oil _ painting _ unreal _ 5 _ daz. _ rpg _ portrait _ extremely _ detailed _ artgerm _ greg _ rutkowski _ greg +anthropomorphic highly detailed group portrait of funny mr bean neon giant cute eyes hermit, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm, bob eggleton, michael whelan, stephen hickman, richard corben, wayne barlowe, trending on artstation and greg rutkowski and alphonse mucha, 8 k +UHD photorealistic studio portrait of a cyborg Angel with hyperrealistic Angel wings, futuristic robot angel, exotic alien features, robotic enhancements, Tim Hildebrandt, Wayne Barlowe, Bruce Pennington, donato giancola, larry elmore, , masterpiece, trending on artstation, , cinematic composition, dramatic pose, studio lighting, sharp, crisp detail, hyperdetailed +a grungy woman with rainbow hair, soft eyes and narrow chin, dainty figure, long hair straight down, torn overalls, short shorts, combat boots, side boob, wet tshirt, raining, basic white background, symmetrical, watercolor, pen and ink, intricate line drawings, by Yoshitaka Amano, Ruan Jia, Kentaro Miura, Artgerm, detailed, trending on artstation, hd, masterpiece, +mahindra thar driving through madagascar with baobabs trees, tribe members chasing for an attach, action scene, an epic fantasy, artgerm and greg rutkowski and alphonse mucha, an epic fantasy, volumetric light, detailed, establishing shot, an epic fantasy, trending on art station, octane render, midsommar +a professional photographic portrait view picture of a minimalist luxurious room, photographic filter unreal engine 5 realistic hyperdetailed 8 k ultradetail cinematic concept art volumetric lighting, fantasy artwork, very beautiful scenery, very realistic painting effect, hd, hdr, cinematic 4 k wallpaper, 8 k, ultra detailed, high resolution, artstation trending on artstation in the style of albert dros glowing rich colors powerful imagery +a fancy portrait of a very attractive succubus by greg rutkowski, beautiful dress, beeple, sung choi, mitchell mohrhauser, maciej kuciara, johnson ting, maxim verehin, peter konig, final fantasy, macro lens, 8 k photorealistic, cinematic lighting, hd, high details, dramatic, dark atmosphere, trending on artstation +a colorful comic noir illustration painting of a cyberpunk girl by sachin teng and sam yang!! and artgerm!! and lois van baarle and ross tran!!. in style of digital art, symmetry, sci fi, hyper detailed. octane render. trending on artstation +chrysta bell, pinup, league of legends, intricate, highly detailed, digital painting, hyperrealistic, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K, art by artgerm and greg rutkowski and alphonse mucha, by Jesper Ejsing +a wacky clown is participating in the running of the bulls in pamplona, by stanley artgerm and greg rutkowski, dramatic lighting, highly detailed, incredible quality, trending on artstation, national geographic photo winner +terrifying otherworldly dimension of the crystalline entities, concept art by filip hodas, john howe, mike winkelmann, jessica rossier, andreas rocha, bruce pennington, 4 k, +very high quality illustration of green hills with clouds in the background, golden hour sunset, purple beautiful sky, anime key visual, official media, illustrated by wlop, extremely detailed, 8 k, trending on pixiv, cinematic lighting, beautiful +The fluffiest little fuzzbutts in the world, huggy wuggy from poppy playtime video game, fullbody, ultra high detailed, glowing lights, oil painting, Greg Rutkowski, Charlie Bowater, Beeple, unreal 5, DAZ, hyperrealistic, octane render, RPG portrait, dynamic lighting, fantasy art, beautiful face +anthropomorphic fluffy fox look like Indiana jones on the hot air balloon at night, clouds around, entire person visible, DnD character, unreal engine, octane render, dramatic lighting, pond, digital art, by Stanley Artgerm Lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman Rockwell, +a young man wearing raybands holding a beer giving a thumbs up with a long beard, real life skin, intricate, elegant, highly detailed, artstation, concept art, smooth, sharp focus, airbrush painted, art by artgerm and greg rutkowski and alphonse mucha +Madonna, the singer, as Medusa snakehair closeup, D&D, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, hearthstone, art by Artgerm and Greg Rutkowski and Alphonse Mucha tarotcard +a whirlwind inside the metaverse, guy, male, man, science, machine face, fashionable haircut, half body, neurochip, android, cyberpunk face, by loish, d & d, fantasy, intricate, elegant, highly detailed, colorful, digital painting, artstation, concept art, art by artgerm and greg rutkowski and alphonse mucha +side profile centered painted portrait, rollerskating monkey, Gloomhaven, matte painting concept art, art nouveau, beautifully backlit, swirly vibrant color lines, fantastically gaudy, aesthetic octane render, 8K HD Resolution +capybara holding a blaster, very very anime!!!, fine - face, realistic shaded perfect face, fine details. anime. realistic shaded lighting poster by ilya kuvshinov katsuhiro otomo ghost - in - the - shell, magali villeneuve, artgerm, jeremy lipkin and michael garmash and rob rey +fork fork fork, symmetry, faded colors, exotic alien features, forestpunk background, tim hildebrandt, wayne barlowe, bruce pennington, donato giancola, larry elmore, masterpiece, trending on artstation, featured on pixiv, cinematic composition, beautiful lighting, sharp, details, hyper detailed, 8 k, unreal engine 5 +landscape with waterfalls and stunning light and cheerful colors, epic composition, cinematic lighting, masterpiece, trending on artstation, very very detailed, masterpiece, stunning +portrait of ronaldo nazario, wearing green soccer clothes, very detailed eyes, hyperrealistic, very detailed painting by glenn fabry, by joao ruas, by artgerm +A lazy steampunk cat jumping over the galaxy, digital illustration, concept art, 8k, trending on artstation +a fantastical translucent!!! small horse made of water and foam, ethereal, noble, radiant, hyperalism, scottish folklore, digital painting, artstation, concept art, smooth, 8 k frostbite 3 engine, ultra detailed, art by artgerm and greg rutkowski and magali villeneuve +ancient queen emma watson, symetrical, by junji ito, diffuse lighting, fantasy, intricate, elegant, highly detailed, lifelike, photorealistic, digital painting, artstation, illustration, concept art, 4 k, smooth, sharp focus, art by john collier and albert aublet and krenz cushart and artem demura and alphonse mucha +aesthetic portrait commission of a of a male fully furry muscular anthro albino lion wearing attractive gay leather harness with a tail and a beautiful attractive hyperdetailed face at golden hour, safe for work (SFW). Character design by charlie bowater, ross tran, artgerm, and makoto shinkai, detailed, inked, western comic book art, 2021 award winning film poster painting +ultra realistic illustration, man in a jacket with two dark glasses, with black hair, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +portrait of one meadow metal horse by gaston bussiere, anna nikonova aka newmilky, greg rutkowski, yoji shinkawa, yoshitaka amano, tsutomu niehi, moebius, donato giancola, geoffroy thoorens, concept art, trending on artstation, featured on pixiv, cinematic composition, 8 k +parrot as a bartender, dimly-lit cozy tavern, fireplace, 8k octane beautifully detailed render, post-processing, extremely hyperdetailed, intricate, epic composition, grim yet sparkling atmosphere, cinematic lighting + masterpiece, trending on artstation, very detailed, vibrant colors +a roman palace reaching to the sky, glorious, epic scene, beautiful, pools, vegetation, in the style of artgerm, gerald brom, atey ghailan and mike mignola, vibrant colors and hard shadows and strong rim light, plain background, comic cover art, trending on artstation +glamorous scorpion portrait, bra, seductive eyes and face, elegant, lascivious pose, very detailed face, studio lighting, photorealism, portrait by Magali Villeneuve and Steve Argyle,Livia Prima,Mucha,dress,fantasy art,beautiful,artstation,trending on artstation,intricate details,alluring,masterpiece +face of a cute alien girl wearing shiny plastic armor in the style of roger dean and alberto vargas and stefan kostic, realistic, sharp focus, 8 k high definition, insanely detailed, intricate, elegant, art by greg rutkowski and artgerm, extreme blur coral reef background +a color pencil sketch of a mysterious plague doctor with a white mask wearing a blue wisards robe, concept art, by greg rutkowski and makato shinkai, by melmoth zdzislaw belsinki craig mullins yoji shinkawa, black light, semi - realistic render, pencil, paint smears, realistic manga, dramatic lighting, d & d design +a beautiful barmaid, dimly lit cozy tavern in the style of Francis Bacon and Syd Mead and Edward Hopper and Norman Rockwell and Beksinski, open ceiling, highly detailed, painted by Francis Bacon, painted by James Gilleard, surrealism, airbrush, Ilya Kuvshinov, WLOP, Stanley Artgerm, very coherent, art by Takato Yamamoto and James Jean +isolated magnolia flowers with no people, colorful, psychedelic, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +jossi of blackpink, king, tarot card, highly detailed, digital painting, smooth, sharp focus, illustration, ultra realistic, 8 k, art by artgerm and alphonse mucha +the most beautiful sunset, giant pink full moon, coherent design, symmetrical, concept art, vivid color, complementary color, golden ratio, detailed, sharp lines, intricate, rainbowshift, by maxfield parrish, by peter mohrbacher, by gustave dore, by arthur rackham, octane render +donald trump, ornate, beautiful, atmosphere, vibe, mist, smoke, chimney, rain, well, wet, pristine, puddles, waterfall, melting, dripping, snow, ducks, creek, lush, ice, bridge, cart, forest, flowers, concept art illustration, color page, 4 k, tone mapping, akihiko yoshida, james jean, andrei riabovitchev, marc simonetti, yoshitaka amano, digital illustration, greg rutowski, volumetric lighting, sunbeams, particles, trending on artstation +fantasy art, animal conceptual artwork, woman with giant fish, surreal painting, illustration dream and imagination concept, mystery of nature +a cute giantess wearing school uniform standing in the city which seem small, bird's eye view, gouache, 8 k wallpaper, strong brush stroke, very high detailed, sharp focus, illustration, morandi color scheme, art station, by krenz cushart +inside a cozy post apocalyptic library, concept art, trending on artstation +baroque acrylic painting of key visual concept art, anime maids in crusade battlefield with early tanks, brutalist fantasy, rule of thirds golden ratio, fake detail, trending pixiv fanbox, palette knife, style of makoto shinkai ghibli takashi takeuchi yoshiyuki sadamoto jamie wyeth james gilleard greg rutkowski chiho aoshima +baroque oil painting, anime key visual full body portrait character concept art, maid nazi ss commander, brutalist grimdark fantasy, kuudere blond hair blue eyes, fascist nationalist, trending pixiv fanbox, rule of thirds golden ratio, makoto shinkai genshin impact studio ghibli jamie wyeth greg rutkowski chiho aoshima +kanye west. in style of yoji shinkawa and hyung - tae kim, trending on artstation, dark fantasy, great composition, concept art, highly detailed, dynamic pose, vibrant colours. +a Japanese modern style luxurious living room, high definition, 8k, intricate and epic concept art, highly detailed, cinematic, +anonymous as elmo, award winning creature photography, extremely detailed, artstation, 8 k, sensual lighting, incredible art, wlop, artgerm +portrait painting of man biting woman neck, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and alphonse mucha +a male half elf in fireproof leather armor wearing a utility belt and goggles, D&D, fantasy, intricate, cinematic lighting, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by Terry Moore and Greg Rutkowski and Alphonse Mucha +portrait painting of a black muscular bloodied indian middle aged woman in river screaming name of god, sari, ultra realistic, concept art, intricate details, eerie, horror, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and alphonse mucha +baroque oil painting full body portrait character concept art, anime key visual of smug young female maid nazi dictator, long straight blonde hair blue eyes, studio lighting delicate features finely detailed perfect face directed gaze, black nazi military uniform, gapmoe kuudere grimdark, trending on pixiv fanbox, painted by greg rutkowski makoto shinkai takashi takeuchi studio ghibli +symmetry!! 1 3 mm film portrait of bearded man, sci - fi -, cyberpunk, blade runner, glowing lights, tech, biotech, techwear!! intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, grain, old photograph +matte painting of a huge swamp, overgrown with lush vines, immaculate scale, greg rutkowski, digital art, trending on artstation, detailed matte painting +a stunning matte portrait of a thicc and voluptuous vampire dressed as a beautiful poison ivy with hair tied in a braid walking through a flowering garden, greenhouse in the background, dark eyeliner, intricate, elegant, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by artgem and jugendstil and greg rutkowski and alphonse mucha, pixv +portrait of ( ( ( vladimir putin ) ) ) inapocalyptic russia with icecream, hyperrealistic, digital concept art, sharp focus, 3 5 mm film, caricature illustration, art by magic realism, art by josephine wall, art by huang guangjian, art by viktoria gavrilenko, art by amanda sage, trending on artstation +pointillism painting of a white and caramel beagle dog playing with dragonfly, bright, god rays, dreamy, trending on artstation +classical oil painting of anime key visual environment concept art of the founding of a nation, trending on artstation, brush strokes, oil, canvas, style of kawacy makoto shinkai jamie wyeth james gilleard edward hopper greg rutkowski, preserved historical +evil magic steampunk sword concept art, trending on artstation 4k +hockey game city location with hockey arena, medical building and office buildings. game illustration, gamedev, game, design, mobile game, aerial view, isometric, blizzard, easports, playrix, nexters, intricate, elegant, pixel perfect, sport game, highly detailed, amazing detail, digital painting, trending on artstation, sharp focus, by irina knk, by ann bruhanova, by zze festa, by tatiana gromova, 4 k +a photorealistic dramatic fantasy render of a beautiful woman alexandra daddario wearing a beautiful intricately detailed japanese monkey kitsune mask and clasical japanese kimono by wlop, artgerm, greg rutkowski, alphonse mucha, epic, beautiful dynamic dramatic dark moody lighting, shadows, cinematic atmosphere, artstation, concept design art, octane render, 8 k +indistinct man with his hand thrust forward, visible threads of magic link his hand to other people's bodies, he's puppeting them, fantasy, digital art, trending on artstation +robot pregnant with a human, cozy atmospheric and cinematic lighting, ultra rendered extreme realism and detail 8 k, highly detailed, realistic, refined, bautiful, fine art photography, hyper realistic, in the style of greg rutkowski, by artgerm, by gustave dore, by marco turini, photorealistic, elegant, sharp focus, majestic, award winning picture, intricate, artstation, +beautiful underwater futuristic city, trending on artstation +photo of a gorgeous blonde female in cyberpunk city, realistic, sharp focus, 8 k high definition, insanely detailed, intricate, elegant, artgerm, greg kutkowski, high contrast dramatic lighting +yoda ( 2 0 2 1 ) walking next to groot ( 2 0 1 7 ). they are friends. photorealistic, digital art, epic fantasy, dramatic lighting, cinematic, extremely high detail, cinematic lighting, trending, artstation, cgsociety, 3 d ue 5, 4 k, hq +portrait of a ruggedly handsome ranger, hands details, muscular, half body, leather, hairy, d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +warhammer 40k, full-lenght portrait of Emperor of Mankind, handsome man in massive gold armor without helmet, beautiful face, long blonde hair, digital art, illustration, fine details, cinematic, highly detailed, octane render, concept art +illustration of an anime girl being mind controlled, by artgerm and wlop and greg rutkowski, digital art, extreme detail, realistic lighting, cinematic composition, concept art, sharp focus, colorful, photorealistic, 8 k +mark zuckerberg as an alien, fantasy art, in the style of artgerm, illustration, epic, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing, vibrant, artgerm, award winning art +a cloaked cyclops wielding a massive sword, smooth, intricate, elegant, digital painting, artstation, concept art, sharp focus, octane render, illustration, art by hirohiko araki, overwatch character, +hyperrealistic photography of a highly detailed and symmetrical gorgeous nordic female scientist constructing a birth machine in the style of Jin Kagetsu, James Jean and wlop, highly detailed, masterpiece, award-winning, sharp focus, intricate concept art, ambient lighting, 8k, artstation +a spaceship flying through space with galaxies in the back, epic lighting, in the art style of arcane, digital art, vector art, trending on artstation, highly detailed +demonic evil cute fourteen year old south asian girl, tomboy, evil smile, freckles!!!, fully clothed, hypnotic eyes, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha, konstantin razumov, by william - adolphe bouguerea +ultra realistic illustration, eva green as persephone, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +a highly detailed epic cinematic concept art CG render digital painting artwork: old dead couple at a decayed gas station surrounded by dark figures. By Greg Rutkowski, in the style of Francis Bacon and Syd Mead and Norman Rockwell and Beksinski, open ceiling, highly detailed, painted by Francis Bacon and Edward Hopper, painted by James Gilleard, surrealism, airbrush, Ilya Kuvshinov, WLOP, Stanley Artgerm, very coherent, triadic color scheme, art by Takato Yamamoto and James Jean +a closeup photorealistic photograph of a cute smiling knitted bernedoodle judge dog dressed in a black gown, presiding over the courthouse. indoors, professional capture, well lit shot. this 4 k hd image is trending on artstation, featured on behance, well - rendered, extra crisp, features intricate detail, epic composition and the style of unreal engine. +a hyper realistic character concept art of a ((cyberpunk real estate agent)) standing by a (For Sale) sign, half body, front facing camera, 4k rendered in Octane, trending in artstation, cgsociety, 4k post-processing highly detailed by wlop, Junji Murakami, Mucha Klimt, Sharandula, Hiroshi Yoshida, Artgerm, Craig Mullins,dramatic, moody cinematic lighting +AN 8K RESOLUTION, MATTE PAINTING OF THE WISE AND ANcIENT alien TURTLE, swimming THROUGH a rainbow nebula BY BOB EGGLETON AND MICHAEL WHELAN. TRENDING ON aRTSTATION, hd, highly detailed, vibrant colors, astrophotography, volumetric lighting, dynamic portrait, wide lens, mass effect fan art +cruising ship sailing at raining night at flooded miniature city, sun is on the rise on the town, cute style garden, octane render, trees, evergreen, patio, garden, wet atmosphere, tender, soft light misty yoshitaka amano, and artgerm +concept art for a futuristic luxury business class suite in a widebody jet, two aisles, earth tones, digital painting, artstation +portrait of betty cooper with fluffy bangs, bangs, 1 9 6 0 s, ponytail, curly bangs and ponytail, rounder face, intricate, elegant, glowing lights, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by wlop, mars ravelo and greg rutkowski +spiky brown very short hair and glasses mage wearing robe, dndbeyond, bright, colourful, realistic, dnd character portrait, full body, pathfinder, pinterest, art by ralph horsley, dnd, rpg, lotr game design fanart by concept art, behance hd, artstation, deviantart, hdr render in unreal engine 5 +Avenida Paulista painted by Greg Rutkowski +master chief from halo fighting aliens, cinematic composition, epic cinematic lighting, realistic, unreal, highly detailed, 8 k, trending artstation, concept art, sharp focus +close-up macro portrait of the dark queen, epic angle, epic pose, symmetrical artwork, photorealistic, iridescent, 3d with depth of field, blurred background. cybernetic phoenix bird, translucent dragon, nautilus. energy flows of water and fire, by Tooth Wu and wlop and beeple. a highly detailed epic cinematic concept art CG render digital painting artwork scene. By Greg Rutkowski, Ilya Kuvshinov, WLOP, Stanley Artgerm Lau, Ruan Jia and Fenghua Zhong, trending on ArtStation, made in Maya, Blender and Photoshop, octane render, excellent composition, cinematic dystopian brutalist atmosphere, dynamic dramatic cinematic lighting, aesthetic, very inspirational, arthouse +sensual beautiful delhi girls wearing western little black dresses at a nightclub, epic scene, by victo ngai, kilian eng vibrant colours, dynamic lighting, digital art, winning award masterpiece, fantastically beautiful, illustration, aesthetically inspired by beksinski and dan mumford, trending on artstation, art by greg rutkowski, 8 k +amazingly detailed semirealism, anthropomorphic pink rabbit character wearing a bucket hat. Cute, kawaii, Cooky, bt21, Sanrio inspired. Beautiful artwork, Rabbt_character, rabbit_bunny, 獣, iconic character splash art, Detailed fur, detailed textures, 4K high resolution quality artstyle professional artists WLOP, Aztodio, Taejune Kim, Guweiz, Pixiv, Instagram, dribbble, ArtstationHD +pennywise giving micheal jackson a red balloon in the movie it, by stephen king, highly detailed, 8 k, artstation, cinematic, concept art, smooth, sharp focus, movie scene +ultra realistic illustration, a full body portrait of deanna troi as death of the endless, the sandman, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +downtown toronto glowing eyes, shamanic poster lsd art, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, frank frazetta +a frogish kaiju on a desolace planet, legendary epic shot, blade runner, by artgerm, julie bell, beeple and Greg Rutkowski, airbrush, concept art, matte painting, 80s, Smooth gradients, octane render, 8k, High contrast, duo tone, depth of field, volumetric lightning, very coherent artwork +Dramatic portraiture of Uuen, the Pictish god of stags, mixed media, trending on ArtStation, by and ArtGerm and Lucian Freud, luminism +incredible beautiful detailed intricate photorealistic painting of a group of friends laughing together. the colors are very vibrant and the people in the photo look very happy. award winning. vibrant colors, funny, personal, positive, visually pleasing, engaging. high resolution. high quality. photorealistic. hq hd. 8 k. trending on artstation. group of friends laughing. award winning +concept art by greg rutkowski, a very tall, and slender man with short black hair, sitting with the crew in the ship's flight deck, brutalist futuristic interior, dark lighting atmosphere, detailed portraits, nostalgic atmosphere, scifi, digital painting, artstation, concept art, smooth, sharp foccus ilustration, artstation hq +It's easy to explain 'cause this world's not tame +owlish empress, D&D, fantasy, portrait, highly detailed, digital painting, trending on artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and magali villeneuve +epic professional digital art of hungry eyes, eerie atmospheric lighting, painted, intricate, detailed, impressive, leesha hannigan, reyna rochin, wayne barlowe, mark ryden, duncan halleck, best on artstation, cgsociety, wlop, pixiv, stunning, gorgeous, much wow, hdr, 4 k, stunning, gorgeous, cinematic, masterpiece +incredible, crossing a mindblowingly beautiful rainbow bridge, energy pulsing, matte painting, artstation, solarpunk metropolis, cgsociety, dramatic lighting, vibrant greenery, concept art, octane render, arnold 3 d render +beautiful woman lying among snakes, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +artwork of a white tiger king with gold crown and blue king suit, concept art, portrait, super detailed, 4 k hd, trending on artstation, digital painted, low contrast, made by greg rutkowski and viktoria gavrilenko +A Maine forest with cats roaming around beautiful lighting during golden hour. 50mm, f/1.8, Realistic details. Ultra HD. 8K V-ray. Octane Render. Unreal Engine 5. Professionally color graded. Concept art. Vibrant colors. fog. Bokeh +a comic book poster of divali celebrations by moebius and makoto shinkai and rossdraws, featured on artstation, pixiv, volumetric lighting, 8 k, highly detailed render, soft glow, crisp lines, f 1 1, sharp focus, +photo of a Dramatic Kathakali male character with traditional headgear painted face wearing futuristic robocop LED goggles and futuristic robot armour with wide traditional ghaghra in the style of stefan kostic, full body, realistic, sharp focus, symmetric, 8k high definition, insanely detailed, intricate, elegant, art by stanley lau and artgerm, Hajime Sorayama, William-Adolphe Bouguereau +vampire the masquerade, fame of thrones, lord, neon, fibonacci, sweat drops, insane, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K, art by artgerm and greg rutkowski and alphonse mucha +hyperdetailed portrait of a stunningly beautiful pink cyberpunk cute european girl made of metals and shiny iridescent gems, bright rainbow nimbus, gold necklace, smoke background inspired by ross tran and masamune shirow and kuvshinov, intricate, photorealistic, octane render, rtx, hdr, unreal engine, dnd digital art by artgerm +3 / 4 view of a portrait of woman with flowy hair, bird wings, confident pose, pixie, genshin impact,, intricate, elegant, sharp focus, illustration, highly detailed, concept art, matte, trending on artstation, bright colors, art by wlop and artgerm and greg rutkowski, marvel comics h 6 4 0 +greg manchess portrait painting of a 2 yorha type a no. 2 as overwatch character!! holding a sword!!, white long hair, organic painting, sunny day, matte painting, bold shapes, hard edges, street art, trending on artstation, by huang guangjian and gil elvgren and sachin teng +dungeons and dragons minotaur character closeup portrait, dramatic light, lake background, 2 0 0 mm focal length, painted by stanley lau, painted by greg rutkowski, painted by stanley artgerm, digital art, trending on artstation +the eldritch knight as a realistic fantasy knight, closeup portrait art by donato giancola and greg rutkowski, digital art, trending on artstation, symmetry!! +epic portrait of snufkin, detailed, nebula skies, digital painting, artstation, concept art, donato giancola, joseph christian leyendecker, wlop, boris vallejo, breathtaking, high details, extremely detailed, sincere face, establishing shot, artistic, hyper realistic, beautiful face, octane render +full body portrait of a korean schoolgirl with long hair and bangs, her hands are thin red tedrils, dramatic lighting, illustration by Greg rutkowski, yoji shinkawa, 4k, digital art, sci-fi horror concept art, trending on artstation +symmetry!! young nicole kidman, machine parts embedded into face, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, 8 k +a child looking at a portal in the hidden garden, scare, environment art, fantasy art, landscape art, in the style of greg rutkowski, illustration, epic, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing +nosferatu staying near body of dead woman, scary, dark, misty, at night, 8 k, detailed, concept art, trending on artstation +polaroid picture, sepia, homeless jon hamm in the streets of los angeles, unshaved, toothless, next to a tent, symmetrical face, fine details, day setting, ethereal, trending on artstation +anime elvis presley, rockabilly anime illustration, rock'n'roll cartoon, professional drawing, trending on pixiv +a cute little girl with a round cherubic face, blue eyes, and short wavy light brown hair smiles as she floats in space with stars all around her. she is an astronaut, wearing a space suit. beautiful painting with highly detailed face by artgerm and quentin blake +Tom Cruise at the king in the desert, beautiful face, fighting in a dark scene, eyes, detailed scene, standing in a heroic figure, Armour and Crown, highly detailed, blood and dust in the air, action scene, cinematic lighting, dramatic lighting, trending on artstation, elegant, intricate, character design, motion and action and tragedy, fantasy, D&D, highly detailed, digital painting, concept art +portrait of a jamaican fisherman sci - fi glowing fishing armor muscular cyberpunk intricate elegant highly detailed digital painting artstation concept art, ocean background, jamaican colors, greg rutkowski, loish, rhads, ferdinand knab, makoto shinkai and lois van baarle, ilya kuvshinov, rossdraws, tom bagshaw +an ugly donkey with eyelashes, fantasy art, in the style of artgerm, illustration, epic, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing, vibrant +pregnant woman under street light, highly detailed, sharp focused, ultra realistic digital concept art by artgerm +baroque oil painting full body portrait character concept art, anime key visual of young female black nazi military uniform maid, long flowing platinum blonde hair blue eyes, finely detailed symmetrical perfect face studio lit delicate features directed gaze, gapmoe kuudere grimdark, trending on pixiv fanbox, painted by greg rutkowski makoto shinkai takashi takeuchi studio ghibli +a award winning half body portrait of a beautiful woman in a croptop and cargo pants with ombre purple pink teal hairstyle with head in motion and hair flying listenin to music on headphones by wlop, paint splatter, outrun, vaporware, shaded flat illustration, digital art, trending on artstation, highly detailed, fine detail, intricate +draco malfoy, clash royal style characters, unreal engine 5, octane render, detailed, brawl stars, cinematografic, cinema 4 d, artstation trending, high definition, very detailed +some kittens playing around in a room with yellow background color filled with a fridge. animal cat. digital art. artstation. realistic. vibrant. illustration. in the style of pixar movie. octane render. art by artgerm and greg rutkowski and alphonse mucha. volumetric lighting. +a pretty smiling blonde girl with heart - shaped sunglasses dressed in pink shiny clothes is walking over water, sun set and skyscrappers in the background, art by guweiz, dramatic lighting, highly detailed, incredible quality, trending on artstation +cinematic portrait, captin falcon from smash bros, from left, head and chest only, desaturated, tim hildebrandt, wayne barlowe, bruce pennington, donato giancola, larry elmore, oil on canvas, masterpiece, trending on artstation, featured on pixiv, cinematic composition, dramatic pose, beautiful lighting, sharp, details, hyper - detailed, hd, 4 k +cypher dark souls blood borne fashion photograph, portrait close up, glowing epcot, rei ayanami, final fantasy marlboro, reptile eye of providence, alien brainsucker by karol bak, zdzisław beksinski, daft punk mf boom helmet, kodak portra 4 0 0, 8 k, highly detailed, britt marling style 3 / 4 photographic close, illuminati pyramid, female anime character, druid wizard, giygas organic being, portrait, skeleton, kannon mindar android, sparking beeple, from artstation, anime render, rutkowski of symmetrical art, android wlop, station, very coherent punk, glitchcore, iridescent on greg cyber the cinematic, art, artwork. cinematic, 8 k, unreal albedo accents, art, high hyper epcot, inside realism, hyper wizard very male octane broken hellscape, of mindar detail, greg overlord, artwork, rutkowski colossus, symmetrical key detail, coherent trending japan, artwork, space hornwort, artwork. abstract, druid druid, artstation, futurescape, on render, shadows robot, glitch forest organic, character, spell, render, key octane render, accents a concept library casting iridescent abstract. by octane intricate realism, octane dan from intricate mask, trending intricate intricate high render, art, gems, mumford. wu, tooth engine cannon beeple, 8 k, a oni +beautiful black girl magic, nature goddess with brown skin in front of nebulae bursting halos, crisp digital painting by artgerm by mucha by caravaggio and face by wlop +goth anime clown in mini skirt and crop top intricate, extremely detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, intimidating lighting, incredible art, face and body +Twin Peaks, of Michael Shannon the mechanic discovering a man dressed as a Furry in the woods, mysterious creepy, poster artwork by Michael Whelan, Bob Larkin and Tomer Hanuka, from scene from Twin Peaks, simple illustration, domestic, nostalgic, from scene from Twin Peaks, clean, full of details, by Makoto Shinkai and thomas kinkade, Matte painting, trending on artstation and unreal engine, super clean, fine detail, cell shaded, +realistic character concept, japanese queen with lots of jewelry in the face, elegant pose, scifi, illustration, symmetrical, artstation, cinematic lighting, hyperdetailed, cgsociety, 8 k, high resolution, charlie bowater, tom bagshaw, single face, insanely detailed and intricate, beautiful, elegant, golden ratio, dark fractal background, vfx, postprocessing, soft lighting colors scheme, fine art photography, hyper realistic, photo realistic +magic : the gathering fantasy character concept art of a ball of rice with a menacing facial expression, by frank frazetta and marco bucci, high resolution. dark fantasy forest in the background, fantasy coloring, intricate, digital painting, artstation, smooth, sharp focus +pregnant woman under street light, highly detailed, sharp focused, ultra realistic digital concept art by Alyssa Monks, Ruan Jia, Stanley Artgerm +a grim dark fantasy town seen from the gutters, dnd encounter, dark fantasy, rain, atmospheric lighting, extremely detailed, no people, photorealistic, octane render, 8 k, unreal engine 5. art by artgerm and greg rutkowski and alphonse mucha +mf doom with reptile eyes, fallout power armor exploding into fractals, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, frank frazetta +Very very very very highly detailed epic central composition portrait of face with venetian mask, golden, intricate, dystopian, sci-fi, extremely detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, intimidating lighting, incredible art by Tokujin Yoshioka and Anton Pieck +Michael Fassbender in white armor, intricate, epic lighting, hyper realistic, white short hair, character concept art, cinematic, artgerm, artstation trending. +a hyper - realistic character concept art portrait of a computer man, depth of field background, artstation, award - winning realistic sci - fi concept art by jim burns and greg rutkowski, beksinski, a realism masterpiece, flesh - tone color palette, james gilleard, bruegel, alphonse mucha, and yoshitaka amano. +tundra, digital art, concept art, magic fantasy, vibrant colors, high contrast, highly detailed, trending on artstation, 8k, andreas rocha, sylvain sarrailh, darek zabrocki, finnian macmanus, dylan cole, liang mark, albert bierstadt, sung choi, peter mohrbacher, greg rutkowski, studio ghibli +beautiful full body Emma Watson smiling, art by lois van baarle and loish and ross tran and rossdraws and sam yang and samdoesarts and artgerm, digital art, highly detailed, intricate, sharp focus, Trending on Artstation HQ, deviantart, unreal engine 5, 4K UHD image +a stunning GTA V loading screen with a beautiful woman with ombre hairstyle in purple and pink blowing in the wind, city streets, golden ratio, digital art, trending on artstation +A cyberpunk cyborg girl with big and cute eyes, fine-face, realistic shaded perfect face, fine details. not anime. Realistic shaded lighting poster by Ilya Kuvshinov katsuhiro, magali villeneuve, artgerm, Jeremy Lipkin and Michael Garmash, Rob Rey and Kentarõ Miura style, trending on art station +peaceful elven forest, thick forest filled with elven warriors, by alan lee, michal karcz, smooth details, lord of the rings, game of thrones, smooth, detailed terrain, oil painting, trending artstation, concept art, fantasy matte painting +a lisa frank fashion model mcdonalds princess microwaved super deluxe big mac happymeal with diet coke and a large order of fries, gothic, highly detailed, digital painting, artstation, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha and william - adolphe bouguereau +collie as odin, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, illustration, hearthstone, art by artgerm and greg rutkowski and alphonse mucha, simon stalenhag, hyperreal +a beautiful daft punk humanoids with freckled cheeks, cyber neon lighting, futurism, intricate futuristic jewelry accessories, cyberpunk glossy white latex swimsuit, profile posing, hyper photorealistic, crispy quality, digital photography, trending in artstation, trending in pinterest, cinematic, 4 k ultra hd, art by pascal blanche, art by greg rutkowski, +portrait sci-fi art by Ruan Jia and Raymon Swanland, a glowing alien neon glass orb floating above the hand of a soldier, solar flares, detailed and intricate futuristic environment, cyberpunk, neon color bioluminescence, transparent reflective metal, dramatic lighting, cinematic, high technology, highly detailed portrait, digital painting, artstation, concept art, smooth, sharp focus, illustration, Artstation HQ +Rose Gold intricate lace smoke portrait, geometric watercolor art by peter mohrbacher and artgerm, radiant halo of light +skinny male fantasy alchemist, long dark hair, 1 9 th century, elegant, highly detailed, intricate, smooth, sharp focus, artstation, digital paining, concept art, art by donato giancola, greg rutkowski, artgerm, cedric peyravernay, valentina remenar, craig mullins +cute friendly shrine maiden by charlie bowater and titian and artgerm, intricate, face, japanese shrine, elegant, pink mist, beautiful, highly detailed, dramatic lighting, sharp focus, trending on artstation, artstationhd, artstationhq, unreal engine, 4 k, 8 k +cute fisherman tom daley, natural lighting, path traced, highly detailed, high quality, digital painting, by don bluth and ross tran and studio ghibli and alphonse mucha, artgerm +Boris Johnson as Jack Sparrow, Boris Johnson hairstyle, realistic portrait, symmetrical, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +billionaire's yacht adopted as a vacation spot for coal miners a Mandelbrot fractal by Craig Mullins, ilya kuvshinov, krenz cushart, artgerm trending on artstation by Edward Hopper and Dan Mumford and WLOP and Rutkovsky, Unreal Engine 5, Lumen, Nanite +hisoka, young tom hiddleston, cel - shaded animesque art by artgerm and greg rutkowski and alphonse mucha, smooth white skin, smirking face, reddish hair, d & d, fantasy, feminine portrait, highly detailed, digital painting, trending on artstation, concept art, sharp focus, illustration +The eye of cthulu from Terraria, 3d render trending on artstation +photographic portrait of a widow, highly detailed, digital painting, Trending on artstation , HD quality, by artgerm and greg rutkowski and alphonse mucha, dramatic light, octane +portrait of megan fox as pinhead, bald, hellraiser, hell, intricate, headshot, highly detailed, digital painting, artstation, concept art, sharp focus, cinematic lighting, illustration, art by artgerm and greg rutkowski, alphonse mucha, cgsociety +lady assassin wearing cyberpunk streetwear, cybernetic legs, detailed portrait, 4 k, vivid colours, concept art by wlop, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality +a monk meditating, in the style of tomasz alen kopera and fenghua zhong and peter mohrbacher, mystical colors, rim light, beautiful lighting, 8 k, stunning scene, raytracing, octane, trending on artstation +goddess of war, accurate anatomy, IFBB fitness body, only two hands, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K, art by art by artgerm and greg rutkowski and edgar maxence +a portrait of a beautiful cybernetic woman meditating in lotus pose, wires, cyberpunk concept art by josan gonzales and philippe druillet and dan mumford and enki bilal and jean claude meziere +symmetry!! portrait of mark zuckerberg, hairless!!, fantasy, medieval wear, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +professional concept art portrait of a masked diesel punk man in a dark room by artgerm and greg rutkowski ( thin white border ). an intricate, elegant, highly detailed digital painting, concept art, smooth, sharp focus, illustration, in the style of cam sykes, wayne barlowe, igor kieryluk. +margot robbie, manga cover art, detailed color portrait, artstation trending, 8 k, greg rutkowski +a portrait of an anthropomorphic cyberpunk mouse holding a can of beer, cyberpunk!, fantasy, elegant, digital painting, artstation, concept art, matte, sharp focus, illustration, art by josan gonzalez +skeleton man walking forward with explosion behind him, science fiction industrial hard science concept art, 8K render octane high definition cgsociety, photorealistic, unreal engine +a cloaked adventure standing in a winding road, gas street lamps. Country road, country landscape, fields, fields, the ruins of one small barn, wide view, desolate. digital illustration, very vibrant colors, soft lighting, adventurous, atmospheric lighting, 8K, octane render. By Makoto Shinkai, Stanley Artgerm Lau, WLOP, Rossdraws, James Jean, Andrei Riabovitchev, Marc Simonetti, krenz cushart, Sakimichan, D&D trending on ArtStation, digital art. +vibrant colorful vaporwave geometry symmetry bauhaus poster, etching by gustave dore, intricate, sharp focus, illustration, highly detailed, digital painting, concept art, masterpiece +Abandoned medieval castle, art by Quentin Mabille , trending on artstation, artstationHD, artstationHQ, 4k, 8k +Boris Johnson as Wolverine, portrait, X man costume, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +Mikasa Ackerman, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by greg rutkowski and alphonse mucha +lateral portrait of samurai, sci - fi, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +Aly Michalka as a stunning , beautiful retro SCI-FI space heroine 1985 , movie poster, intricate, elegant, highly detailed, centered, digital painting, trending on artstation, concept art, smooth, sharp focus, illustration, art by raphael lacoste ,eddie mendoza ,alex ross, WLOP +a visual representation of a place evoked by the song titles of the album kryptos by andreas vollenweider, photorealistic and intricate concept art, 8 k hdr, cinematic lighting +fantasy girl mage in a forest, dramatic fantasy art, by yoshitaka amano, trending on artstation, 4 k, expressive oil painting, close - up face portrait, vivid colors +a portrait of a finely detailed beautiful!!! feminine cyberpunk ghost rider with skull face and long flowing hair made of fire and flames, dressed in black leather, by Alphonse Mucha, designed by H.R. Giger, legendary masterpiece, stunning!, saturated colors, black background, trending on ArtStation +tattoo design, stencil, stencil on paper, tattoo stencil, traditional, beautiful portrait of a traditional Japanese girl with flowers in her hair, upper body, by artgerm, artgerm, artgerm, digital art, cat girl, anime eyes, anime, sexy, super model-s 100 +portrait of a young very beautiful cute tribal woman with a steampunk gun, in a post apocalyptic city overgrown with lush vegetation, by Luis Royo, by Greg Rutkowski, dark, gritty, intricate, head space, volumetric lighting, volumetric atmosphere, concept art, cover illustration, octane render, trending on artstation, 8k +a young attractive Asian woman in the pilot's seat of a massive sci-fi mecha, dramatic pose, LEDs, highly detailed, photorealistic, volumetric lighting, digital art, octane render, in the style of Artgerm and Tom Bagshaw +wolf warrior in red cape and hood, d & d, fantasy, portrait, highly detailed, headshot, digital painting, trending on artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and magali villeneuve +a beautiful young charming asian goddess with sundress and jewelry | | winter, realistic shaded, unpleasant face, good looking, fine details, dior, lv, realistic shaded lighting poster by greg rutkowski, macoto takahashi, magali villeneuve, artgerm, jeremy lipkin and michael garmash +hyperrealistic portrait of a woman monster astronaut, full body portrait, well lit, intricate abstract. cyberpunk, intricate artwork, by Tooth Wu, wlop, beeple. octane render,in the style of Jin Kagetsu, James Jean and wlop, highly detailed, sharp focus, intricate concept art, digital painting, ambient lighting, 4k, artstation +tracer overwatch portrait, close up, concept art, intricate details, highly detailed photorealistic portrait by michael komarck, joel torres, seseon yoon, artgerm and warren louw +a grim reaper with a crt monitor for a head. the monitor has a blue screen with white letters on it. by frank frazetta, simon bisley, brom, concept art, octane render, unreal engine 5, highly detailed, high quality, 8 k, soft lighting, realistic face, path traced +blender gloomy colossal ruined server room in datacenter robot figure automata headless drone robot knight welder posing pacing fixing soldering mono sharp focus, emitting diodes, smoke, artillery, sparks, racks, system unit, motherboard, by pascal blanche rutkowski artstation hyperrealism cinematic dramatic painting concept art of detailed character design matte painting +a photograph of a robot endoskeleton submerged and rusted in the water, cinematic, volumetric lighting, f 8 aperture, cinematic eastman 5 3 8 4 film, photorealistic by greg rutkowski, by stanley artgerm, by alphonse mucha +hyper detailed ultra sharp, trending on artstation, vibrant aesthetic, bloodwave, colorful, psychedelic, ornate, intricate, digital painting, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and h. r. giger, 8 k +gothic bell tower, view from above. in style of greg rutkowski, jesper ejsing, makoto shinkai, trending on artstation, fantasy, great composition, concept art, highly detailed, scenery, 8 k, behance. +ned kelly, extremely detailed, artstation, 8 k, sensual lighting, incredible art, wlop, artgerm +a girl in times square new york, very sexy outfit, very anime, medium shot, visible face, detailed face, perfectly shaded, atmospheric lighting, by makoto shinkai, stanley artgerm lau, wlop, rossdraws +full-body baroque and cyberpunk glass sculpture of attractive muscular iridescent Nick Jonas as a humanoid deity wearing a thin see-through plastic hooded cloak sim roupa, posing like a superhero, glowing pink face, crown of white lasers, large diamonds, swirling black silk fabric. futuristic elements. oozing glowing liquid, full-length view. space robots. human skulls. throne made of bones, intricate artwork by caravaggio. Trending on artstation, octane render, cinematic lighting from the right, hyper realism, octane render, 8k, depth of field, 3D +breathtaking detailed soft painting of silver hours of sun, caresses on pepper plains, the hand of the country on my shoulder, rembrandt style, elegant, highly detailed, artstation, concept art, matte, sharp focus, art by tom bagshaw, and greg rutkowski +Emma Watson as a dune princess, sci-fi, amber eyes, face, long hair, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +game of thrones, masterpiece, pinup, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K +one beautiful symmetrical close up head shoulder face portrait android woman time machine axonometric mechanical fantasy intricate elegant highly detailed in volumetric void of latent space, golden turquoise steampunk, axonometric high contrast cinematic light, mystical shadows, digital painting, smooth, sharp focus, divine realm of gods, octane render, photographic, concept art, artist leonardo davinci, unreal engine 8 k +arnold schwarzenegger surfing inside erupting volcano, stunning scene, 8 k, extremely detailed digital painting, depth, bright colors, trending on artstation +a photorealistic dramatic fantasy render of a beautiful woman billie eilish wearing a beautiful intricately detailed japanese monkey kitsune mask and clasical japanese kimono by wlop, artgerm, greg rutkowski, alphonse mucha, epic, beautiful dynamic dramatic dark moody lighting, shadows, cinematic atmosphere, artstation, concept design art, octane render, 8 k +Portrait of a space astronaut monkey, fantasy, intricate, highly detailed, digital painting, trending on artstation, sharp focus, illustration, style of Stanley Artgerm +goddess of death, braids, decaying face, neon hair, intricate illuminated jewellery, digital painting, surrealism, extreme detail, cinematic lighting, trending on artstation, by hans zatzka +a zombie teenager staring at their phone, tristan eaton, victo ngai, artgerm, rhads, ross draws +realistic detailed face portrait of a rugged male wizard with black hair wearing a hooded cloak by alphonse mucha, ayami kojima, amano, greg hildebrandt, and mark brooks, male, masculine, art nouveau, neo - gothic, gothic, character concept design +a shadowy figure in tattered robes sees another figure in the distance, in an alien desert during a sandstorm ; tension, creepy mood, uneasy atmosphere, weird fiction art, breathtaking digital illustration, cinematic lighting, striking perspective, aesthetic composition, trending on artstation +an epic painting minion looking like elon musk presenting new tesla, pencil drawing, perfect composition, golden ratio, beautiful detailed, photorealistic, digital painting, concept art, smooth, sharp focus, illustration, artstation trending, octane render, unreal engine +Hedgehog magus, Tzeentch, portrait, nature, fairy, forest background, magic the gathering artwork, D&D, fantasy, cinematic lighting, centered, symmetrical, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, volumetric lighting, epic Composition, 8k, art by Akihiko Yoshida and Greg Rutkowski and Craig Mullins, oil painting, cgsociety +mermaid emma watson, perfectly-centered-painting of emma watson, sweaty, dynamic action pose, insane, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, Unreal Engine 5, 8K, art by artgerm and greg rutkowski and alphonse mucha +painting of hybrid between cat & dragon & snake & fox, intercrossed animal, by zdzislaw beksinski, by lewis jones, by mattias adolfsson, cold hue's, warm tone gradient background, concept art, beautiful composition, digital painting +character portrait of a raven angel of night with iridescent black raven wings wearing robes, lord of change, by peter mohrbacher, mark brooks, jim burns, marina abramovic, wadim kashin, greg rutkowski, trending on artstation +girl sitting on a stair under a vine rack, many green plant and flower gowing on it, illustration concept art anime key visual trending pixiv fanbox by wlop and greg rutkowski and makoto shinkai and studio ghibli +giant skeletal ghoul devouring a mountain of skulls, digital painting, mixed media, trending on artstation and deviantart, epic composition, highly detailed, 8 k +portrait of jean baudrillard, soft hair, muscular, half body, leather, d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +hacker girl sits at an apple ] [ e, realistic shaded lighting poster by ilya kuvshinov katsuhiro otomo, magali villeneuve, artgerm, jeremy lipkin and michael garmash and rob rey +movie still macro close photo of koala selling nft, by weta disney pixar greg rutkowski wlop ilya kuvshinov rossdraws artgerm octane render iridescent, bright morning, liosh, mucha +a coffee shop store in The City of Ukraine at night with a few customers, extreme plus resolution fantasy concept art, intricate details to everything visible, sharp lighting, Dramatic light by denis villeneuve, strong emphasis on alphonse mucha, Makoto Shinkai +the interior of a store that sells board games and sushi, intricate, digital painting, masterpiece, rending on artstation, octane render, art by artgerm and greg rutkowski and alphonse mucha and craig mullins and James Jean and Andrei Riabovitchev and Marc Simonetti and peter mohrbacher +danny devito as wolverine, oil on canvas portrait, octane render, trending on artstation +portrait painting of male evil demonic cult member, agony, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and alphonse mucha +a snoop dogg wearing sun glasses tennis ball monster, snoop dogg tennis ball head, smoking, smoke, monster teeth, colorful, chalk digital art, fantasy, magic, chalk, trending on artstation, ultra detailed, professional illustration by basil gogos +dusk land dark city filled with shadow people, desolate, gloomy, intricate, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski +portrait of a beautiful woman wearing a sari dress, holding a bouquet of flowing flowers, drenched body, wet dripping hair, emerging from the water, fantasy, regal, fractal crystal, fractal gems, by stanley artgerm lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman rockwell +astronaut drifting in space, artwork by greg rutkowski +book cover!!!!!!!!!!!!, old bridge, fantasy forest landscape, fantasy magic, light night, intricate, elegant, sharp focus, illustration, highly detailed, digital painting, concept art, matte, art by wlop and artgerm and ivan shishkin and andrey shishkin, masterpiece +a beautiful hyperrealistic detailed 3D render of a burning monument, by Anton Otto Fischer, Atey Ghailan, genzoman, unreal engine, octane render, gigantic, 3D, brilliantly coloured, intricate, ultra wide angle, trending on artstation, embers, smoke, dust, dusk, volumetric lighting, HDR, polished, micro details, ray tracing, 8k +close-up macro portrait of the face of a beautiful princess with ram skull mask, epic angle and pose, symmetrical artwork, 3d with depth of field, blurred background, cybernetic jellyfish female face skull phoenix bird, translucent, nautilus, energy flows of water and fire. a highly detailed epic cinematic concept art CG render. made in Maya, Blender and Photoshop, octane render, excellent composition, cinematic dystopian brutalist atmosphere, dynamic dramatic cinematic lighting, aesthetic, very inspirational, arthouse. y Greg Rutkowski, Ilya Kuvshinov, WLOP, Stanley Artgerm Lau, Ruan Jia and Fenghua Zhong +a super realistic dragon that is on fire standing dramatically on a destroyed city, ultrawide shot, surreal, sharp focus, digital art, epic composition, concept art, dynamic lighting, intricate, highly detailed, 8 k, unreal engine, blender render +man in suit launching the nukes, matte painting concept art, baroque, beautifully backlit, swirly vibrant color lines, fantastically gaudy, aesthetic octane render, 8 k hd resolution, by caravaggio and diego velazquez +an extremely psychedelic portrait of SalvadorDali, by Raphael Hopper, and Rene Magritte. Extremely Highly detailed, Occult, funny, humorous, humor, hilarious, funny, entertaining, magical, trending on artstationHQ, LSD, diffuse lighting, fantasy, intricate, elegant, highly detailed, lifelike, photorealistic, digital painting, artstation, illustration, concept art, smooth, sharp focus, art by John Collier and Albert Aublet and Krenz Cushart and Artem Demura and Alphonse Mucha and Giuseppe Arcimboldo +inside an etheral atompunk city, highly detailed, 4k, HDR, award-winning, octane render, trending on artstation, volumetric lighting +subspace emissary, jungle groove, constellation - based cathedral, octane render, trending on artstation, ray - tracing, subsurface scattering, 4 k, high quality desktop wallpaper +a dream of being trapped underwater, thalassophobia, fear of the ocean, open water, imagination, dream, concept art, trending on artstation, highly detailed +an anime portait shogun knight with a lightsaber halberd, dark metal armor, and a tattered cape, by stanley artgerm lau, wlop, rossdraws, james jean, andrei riabovitchev, marc simonetti, and sakimichan, trending on artstation +postmodern zakopane designed by louis sullivan, still from a movie, photo art, artgerm, trending on artstation +a beautiful action portrait of a handsome DnD-ranger hunting in a forest, face is brightly lit, by Greg Rutkowski and Raymond Swanland, Trending on Artstation, ultra realistic digital art +jim carrey, portrait shinkai makoto studio ghibli studio key hideaki anno sakimichan stanley artgerm lau rossdraws james jean marc simonetti elegant highly detailed digital painting artstation pixiv +a man tied to a pillar by jack russel terrier, highly detailed, hyperrealistic digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +a professional painting of an russian young blonde girl intricate, wearing russian ancient folk dress, elegant, digital painting, concept art, smooth, sharp focus, finely detailed illustration, beautifully framed, from Metal Gear, in the style of Artgerm and Greg Rutkowski and William-Adolphe Bouguerea +soaring woman wearing a round mask hiding her face with many thick long blades behind head. dressed in a long robe with wide sleeves and making anjali mudra gesture. highly detailed, symmetric, concept art, saturated colors, masterpiece, fantasy art, hyperdetailed, hyperrealism, art by zdzisław beksinski, arthur rackham, dariusz zawadzki, larry elmore +ancient queen billie eilish, symetrical, diffuse lighting, fantasy, intricate, elegant, highly detailed, lifelike, photorealistic, digital painting, artstation, illustration, concept art, 4 k, smooth, sharp focus, art by john collier and albert aublet and krenz cushart and artem demura and alphonse mucha +a gorgeous kanye west photo, professionally retouched, soft lighting, realistic, smooth face, full body shot, torso, perfect eyes, wide angle, sharp focus on eyes, 8 k high definition, insanely detailed, intricate, elegant, art by artgerm and jason chan and mark litvokin +a bear and a bunny chimera with the size and strength of a bear, The white color and long bunny ears of a bunny and golden brown antlers. Concept art. Fantasy. Trending on artstation. Masterpiece. By Karlkka. By Greg Rutkowski James Gurney +beautiful anime girl with short white hair, wearing lab coat and glasses, holding a clipboard, standing inside a research facility, character portrait, 1 9 6 0 s, long hair, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by wlop, charlie bowater and alexandra fomina +Manga cover portrait of an extremely cute and adorable beautiful curious happy puppy smelling a flower, summer vibrance, 3d render diorama by Hayao Miyazaki, official Studio Ghibli still, color graflex macro photograph, Pixiv, DAZ Studio 3D +concept art of fried egg, highly detailed painting by dustin nguyen, akihiko yoshida, greg tocchini, greg rutkowski, cliff chiang, 4 k resolution, trending on artstation, 8 k +Bob Dylan design, character sheet, Kim Jung Gi, Greg Rutkowski, Zabrocki, Karlkka, Jayison Devadas, Phuoc Quan, trending on Artstation, 8K, ultra wide angle, zenith view, pincushion lens effect +dichroic ant axolotl snail bug bee fly worm caterpillar fish, (((artstation, concept art, smooth, sharp focus, artgerm, Tomasz Alen Kopera, Peter Mohrbacher, donato giancola, Joseph Christian Leyendecker, WLOP, Boris Vallejo))), octane render, unreal engine, 3d render, , octane render, nvidia raytracing demo, grainy, muted +sojourn from overwatch, african canadian, gray hair, character portrait, portrait, close up, concept art, intricate details, highly detailed, vintage sci - fi poster, retro future, vintage sci - fi art, in the style of chris foss, rodger dean, moebius, michael whelan, and gustave dore +open treasure chest with the greatest riches on earth, deep focus, d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, hearthstone, art by artgerm and greg rutkowski and alphonse mucha +hunter woman walking across foggy river, unreal engine 5, art by artgerm and greg rutkowski and alphonse mucha, global illumination, detailed and intricate environment, hyperrealistic, volumetric lighting, epic cinematic shot, perfectly defined features, ambient occlusion +psychedelic ; trippy ; acid trip ; artgerm ; salvadore dali ; surreal ; abstract ; lsd ; jesus christ ; ascension ; symmetrical ; mathematical +girl floating on the night sky, gaint planet in the background, illustration concept art anime key visual trending pixiv fanbox by wlop and greg rutkowski and makoto shinkai and studio ghibli +a strange alien fruit, photorealistic, 8 k, professional food photography, volumetric lighting, trending on artstation +painting of hybrid between bear & snake, animal has snake body, intercrossed animal, by zdzislaw beksinski, by lewis jones, by mattias adolfsson, cold hue's, warm tone gradient background, concept art, beautiful composition, digital painting +a professional photographic view picture of a alley in space, photographic filter unreal engine 5 realistic hyperdetailed 8 k ultradetail cinematic concept art volumetric lighting, very beautiful scenery, very realistic effect, hd, hdr, cinematic 4 k wallpaper, 8 k, sharp focus, octane render, ultra detailed, high resolution, artstation trending on artstation in the style of albert dros glowing rich colors powerful imagery +mulan, d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, hearthstone, art by artgerm and greg rutkowski and alphonse mucha +'' Portrait of Beautiful blonde Slavic woman in her early 30�s, league of legends, LOL, fantasy, d&d, digital painting, artstation, concept art, sharp focus, illustration, art by greg rutkowski and alphonse mucha '' +beautiful cottagecore kim kardashian holding a adidas yeezy shoe. intricate, elegant. highly detailed, digital painting, artstation, concept art, smooth, sharp, focus, illustration. . art by artgerm and greg rutkowski and alphonse mucha +symmetry, multiple humans in solid silhouettes, saluting, dancing, interacting and posing, mooc, organic and intricate, elegant, highly detailed, concept art, sharp focus, illustration, high contrast, long shadows, painted with colour on white, 8 k +cinematic bust portrait of psychedelic cyborg, head and chest only, exotic alien features, Tim Hildebrandt, Wayne Barlowe, Bruce Pennington, donato giancola, larry elmore, oil on canvas, masterpiece, trending on artstation, featured on pixiv, cinematic composition, dramatic pose, beautiful lighting, sharp, details, hyper-detailed, HD, HDR, 4K, 8K +a highly detailed illustration of cute smug pink haired pale girl with curved horns wearing oversized pink hoodie, dramatic smirk pose, intricate, elegant, highly detailed, centered, soft light, character design, cushart krenz, digital painting, artstation, concept art, smooth, sharp focus, league of legends concept art, wlop. +portrait of a young mila kunis in front of a cyberpunk city, dramatic light, city background, sunset, high contrast, sharp, painted by stanley lau, painted by greg rutkowski, painted by stanley artgerm, digital art, trending on artstation +portrait close up of guy, concentrated look, symmetry, long hair. d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, art by artgerm and greg rutkowski and alphonse mucha, boris vallejo + law contrasts, fantasy concept art by Jakub Rozalski, Jan Matejko, and J.Dickenson +professional concept art ethereal ghostlike valkyrie figure fluid simulation in houdini dancing in dark smoke robes and silk veils by ilm, paolo roversi, nick knight, amy judd, beautiful simplified form in turbulent movement, dark studio background, turner, romantic, trending on artstation, hyperrealism, matte painting, dutch golden age, fine detail, cgsociety +portrait Anime batman cosplay girl cute-fine-face, pretty face, realistic shaded Perfect face, fine details. Anime. realistic shaded lighting by katsuhiro otomo ghost-in-the-shell, magali villeneuve, artgerm, rutkowski Jeremy Lipkin and Giuseppe Dangelico Pino and Michael Garmash and Rob Rey +hyperrealism, detailed textures, photorealistic 3 d, a young boy walking down the street holding a worn out teddy bear, ultra realistic, cinematic, intricate, cinematic light, concept art, illustration, art station, unreal engine 8 k +nuclear power plant, colorful, sci-fi, clean, utopia, surrounded by wilderness, sunset, octane render, substance painter, zbrush, trending on artstation, 8K, highly detailed. +Defect from Slay the Spire, concept art, by Odilon Redon +insanely detailed procedural render expressive scene of chrome spacesuits protecting the dancing nudibranch girl from certain doom as the planet they orbit sends spores attack them, photorealism, sharp focus, award winning, tristan eaton, victo ngai,, maxfield parrish, artgerm, koons, ryden, intricate details, 3 / 4 view, bokeh +portrait art of Gene Kelly 8k ultra realistic , lens flare, atmosphere, glow, detailed,intricate, full of colour, cinematic lighting, trending on artstation, 4k, hyperrealistic, focused, extreme details,unreal engine 5, cinematic, masterpiece +portrait painting of a post - apocalyptic bald androgynous teenager with white eyes and a green aura around his head, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and charlie bowater and magali villeneuve and alphonse mucha +ancient neon monster portrait, intricate artwork by josan gonzalez, artgerm, h. r. giger, kilian eng, very coherent artwork, cinematic, hyper realism, vibrant, octane render, unreal engine, 8 k, high contrast, higly detailed black ink outline +twin peaks poster art, portrait of the black lodge has the blue colored rose trapped in a glass box, can david bowie find it, by michael whelan, rossetti bouguereau, artgerm, retro, nostalgic, old fashioned +the beautiful hyper detailed scene render that a beautiful girl lies in the arms of a huge silver dragon alone in the fairyland surrounded by white clouds, in the style of makoto shinkai victo ngai and peter mohrbacher studio ghibli artgerm karol bak beeple, animation style, 8 k hd, dream, ultra wide angle, animation style, 3 drender, hyperdetailed +portrait of a beautiful young fit male angel with curly blond hairs, dressed with fluent clothes, luminous scene, by Greg Rutkowski and alphonse mucha, d&d character, gradient white to cyan, in front of an iridescent background, highly detailed portrait, +a scene of a camper in the desert, a cowboy in the foreground looking epic, full shot, atmospheric lighting, detailed faces, by makoto shinkai, stanley artgerm lau, wlop, rossdraws +lalisa manoban of blackpink, knight armor, tarot card, highly detailed, digital painting, smooth, sharp focus, illustration, ultra realistic, 8 k, art by artgerm and alphonse mucha +feudal japan tokyo street at dusk, raining, detailed reflections, on a postcard, cinematic lighting!!, 4k, trending on artstation, detailed watercolour, rule of thirds, center focus, art by albert bierstadt +concept art by greg rutkowski, a gigantic spear - shaped starship approaches the system, huge and megalithic, plowing through space, frightening and creepy atmosphere, scifi, digital painting, artstation, concept art, smooth, sharp foccus ilustration, artstation hq +beautiful girl a strange wind blew in off the north sea, an eerie susurration that cut across the eastern sea, beautiful portrait, symmetrical, character concept style trending on artstation concept art detailed octane render cinematic photo - realistic 8 k high detailed +the street of a frozen village in ice that never the see the sun again, concept art by makoto shinkai and greg rutkowski, matte painting, trending on artstation +full length photo of a gorgeous young woman in the style of stefan kostic, realistic, sharp focus, 8k high definition, insanely detailed, intricate, elegant, art by stanley lau and artgerm +beautiful sci fi space scene with planets, concept art trending on artstation, volumetric lighting, 8k +brigitte from overwatch, character portrait, portrait, close up, concept art, intricate details, highly detailed, vintage sci - fi poster, retro future, vintage sci - fi art, in the style of chris foss, rodger dean, moebius, michael whelan, and gustave dore +will smith fights against demons dressed as a gladiator and with angel wings, cinematic lighting, highly detailed, concept art, art by wlop and artgerm and greg rutkowski, masterpiece, trending on artstation, 8 k +Till Lindemann crushing planet earth with his teeth. epic game portrait. Highly detailed, highly recommended. fantasy art by Greg Rutkowski +Art nouveau Ferarri, fantasy, intricate galactic designs, elegant, highly detailed, sharp focus, art by Artgerm and Greg Rutkowski and WLOP +walter white as lara croft, digital painting, extremely detailed, 4 k, intricate, brush strokes, mark arian, artgerm, bastien lecouffe - deharme +a swamp viewed from afar with one huge tree in the middle, dark colors, glowing plants, misty background, light rays, sunset!, birds, beautiful lighting, vivid colors, intricate, elegant, smooth, sharp focus, highly detailed digital painting, concept art, cinematic, unreal engine, 4 k wallpaper, svetlin velinov, tarmo juhola, artstation trending +wide angle, mage, sleeping on rock, white grey blue color palette, eyes closed, forest, female, d & d, fantasy, intricate, elegant, highly detailed, long brown hair, digital painting, artstation, octane render, concept art, matte, sharp focus, illustration, hearthstone, art by artgerm, alphonse mucha johannes voss +cinematic portrait of the incredible hulk, only head and chest, intricate, desaturated, Tim Hildebrandt, Wayne Barlowe, Bruce Pennington, donato giancola, larry elmore, maxfield parrish, Moebius, Thomas Ehretsmann, oil on canvas, gouache painting, masterpiece, trending on artstation, cinematic composition, dramatic pose, volumetric lighting, sharp, details, hyper-detailed, HD, 4K, 8K +cinematic bust portrait of futuristic robot from left, head and chest only, exotic alien features, robotic enhancements, desaturated, tim hildebrandt, wayne barlowe, bruce pennington, donato giancola, larry elmore, oil on canvas, masterpiece, trending on artstation, featured on pixiv, cinematic composition, dramatic pose, beautiful lighting, sharp, details, hyper - detailed, hd, hdr, 4 k, 8 k +perfectly detailed wisteria flowers!! blessed by nature with ever - increasing physical mental perfection, symmetrical! intricate, sensual features, highly detailed, biblical divine holy perfection!! digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +portrait of teenage girl with long glossy black hair, blue eyes, glowing skin, fashion model features, fantasy, intricate, elegant, black dress, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by Krenz Cushart and Artem Demura and alphonse mucha +a cinematic detailed painting of a black kid in the woods, volumetric light, surrealism, highly detailed, realistic, retro, in the style of francis bacon and james jean, trending on artstation, painting by Edward Hoper, colorful, realistic, smooth, octane render +concept art from zaha hadid, futuristic, ultra realistic, concept art, intricate details, highly detailed, photorealistic, octane render, 8 k +hyperrealistic mixed media painting of a grungy skull woman with rainbow hair, stitched together, soft eyes and narrow chin, dainty figure, long hair straight down, torn v plunge shirt, short shorts, combat boots, basic white background, side boob, wet tshirt, wet, raining, dim volumetric lighting, 8 k octane beautifully detailed render, post - processing, portrait, extremely hyper - detailed, intricate, epic composition, cinematic lighting, masterpiece, trending on artstation, very very detailed, masterpiece, stunning, +cat theme logo, cat theme banner, cat design, a smiling cat, art photography style, trending on artstation, warm light, lovely and cute, fantasy art, 8 k resolution +cover concept art of the lost sand city, levitating sand, ground view, golden towers, golden pillars, palm trees, space and time, floating objects, post-processing, in the style of Hugh Ferriss, Behance, Artgerm. High detail, ultra realistic render, octane, 3D, photorealism, symmetric, cinematic +male anime character, oni mask, organic, forest druid, dark souls boss, cyber punk, portrait, male anime character, robot, masterpiece, intricate, highly detailed, sharp, technological rings, by james mccarthy, by beeple and johfra bosschart, combination in the style ayami kojima, highly detailed, painting, 3 d render beeple, unreal engine render, intricate abstract, intricate artwork, by tooth wu, wlop, beeple, dan mumford. concept art, octane render, trending on artstation, greg rutkowski very coherent symmetrical artwork. cinematic, key art, hyper realism, high detail, octane render, 8 k, iridescent accents, albedo from overlord, the library of gems, intricate abstract. intricate artwork, by tooth wu, wlop, beeple, dan mumford. concept art, octane render, trending on artstation, greg rutkowski very coherent symmetrical artwork. cinematic, key art, hyper realism, high detail, octane render, 8 k, iridescent accents +Lionel Messi closeup, D&D style, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, art by Artgerm and Greg Rutkowski and Alphonse Mucha +young shadow mage male, joyful, d & d, fantasy, intricate, elegant, full body, highly detailed, digital painting, artstation, concept art, matte, sharp, illustration, hearthstone, art by artgerm and greg rutkowski and alphonse mucha +ultra realistic illustration, emma roberts from last of us, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +A beautiful cosmic entity || VERY ANIME, fine-face, realistic shaded perfect face, fine details. Anime. realistic shaded lighting poster by Ilya Kuvshinov katsuhiro otomo ghost-in-the-shell, magali villeneuve, artgerm, Jeremy Lipkin and Michael Garmash, Rob Rey and Kentar� Miura style, trending on art station +muscular gandhi at the beach, sitting on the sand next to a campfire, with palm trees in the back, by artgerm, ilya kuvshinov katsuhiro villeneuve, jeremy lipkin and michael garmash and rob rey, disney pixar zootopia, by tristan eaton, stanley artgermm, tom bagshaw, greg rutkowski, carne griffiths +A fancy portrait of an attractive humanoid creature by Greg Rutkowski, beeple, Sung Choi, Mitchell Mohrhauser, Maciej Kuciara, Johnson Ting, Maxim Verehin, Peter Konig, final fantasy, macro lens , 8k photorealistic, cinematic lighting, HD, high details, dramatic, dark atmosphere, trending on artstation +headless horseman in a marvel movie, science fiction industrial hard science concept art, 8K render octane high definition cgsociety, photorealistic, unreal engine 5 +a highly detailed metahuman 4 k close up render of a goddess bella hadid monument renaissance in iris van herpen dress schiaparelli in diamonds crystals swarovski and jewelry iridescent in style of alphonse mucha gustav klimt trending on artstation made in unreal engine 4 +closeup portrait shot of a ring wraith in a scenic dystopian environment, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +Redhead Pleiadian alien human beautiful hybrid feminine woman, long gorgeous red hair in loose curls, with stunning green eyes, cute round face and a roundish nose, as a retro futuristic heroine, gorgeous digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and donato giancola and Joseph Christian Leyendecker, Ross Tran, WLOP +gigachad luigi bodybuilder in a expensive dress suit by ilya kuvshinov, ernest khalimov body by krista sudmalis, fantasy character portrait, futuristic town background by laurie greasley, ultra realistic, concept art, intricate details, elegent, digital painting, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, artstation +painting of a gorgeous young woman in the style of Martine Johanna, draped in flowing fabric, colorful energetic brush strokes, realistic, sharp focus, 8k high definition, insanely detailed, intricate, elegant, art by Martine Johanna and artgerm +l � lawliet, hunchback, death note, d & d, fantasy, portrait, highly detailed, headshot, digital painting, trending on artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and magali villeneuve and wlop +a portrait of Chiefkeef in front of an Art Nouveau mandala wearing a huge elaborate detailed ornate crown made of all types of realistic colorful flowers, turban of flowers, sacred Geometry, Golden ratio, surrounded by scattered flowers peonies dahlias lotuses roses and tulips, photorealistic face, Cinematic lighting, rimlight, detailed digital painting, Portrait, headshot, in style of Alphonse Mucha, Artgerm, WLOP, Peter Mohrbacher, William adolphe Bouguereau, cgsociety, artstation, Rococo and baroque styles, symmetrical, hyper realistic, 8k image, 3D, supersharp, pearls and oyesters, turban of vibrant flowers, satin ribbons, pearls and chains, perfect symmetry, iridescent, High Definition, Octane render in Maya and Houdini, light, shadows, reflections, photorealistic, masterpiece, smooth gradients, no blur, sharp focus, photorealistic, insanely detailed and intricate, cinematic lighting, Octane render, epic scene, 8K +percy jackson in cyberpunk city, 4 k, trending on artstation. +wonderdream faeries lady feather wing digital art painting fantasy bloom vibrant style mullins craig and keane glen and apterus sabbas and guay rebecca and demizu posuka illustration character design concept colorful joy atmospheric lighting butterfly +Boris Johnson as Thor with hammer Mjolnir, Boris Johnson hairstyle, full body realistic portrait, highly detailed, muscular body, digital painting, artstation, concept art, smooth, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha +An ancient Iranian fortress as Far Cry 4 concept art, spring season, beautiful, gorgeous buildings, , concept art by Viktor Vasnetsov, concept art, ancient era, warm lighting, soft by Ivan Shishkin, Dimitri Desiron and Antonio Lopez Garcia, hyperborea, high resolution, trending on artstation, +high detailed white space station interior a statue jesus on cross made of red marble, perfect symmetrical body, full body shot, inflateble shapes, wires, tubes, veins, jellyfish, white biomechanical details, wearing epic bionic cyborg implants, masterpiece, intricate, biopunk, vogue, highly detailed, artstation, concept art, cyberpunk, octane render +Award-Winning. Trending on Artstation. 8K. Corrupted Knight infected with black obsidian glowing red. Angular. Sharp. Ready for battle. +2 0 year old ethiopian man, sitting on a black corvette, counting money, portrait, elegant, intricate, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by konstantin korovin and daniel f. gerhartz and john howe +beautiful ethereal cyberpunk jennifer lawrence, art nouveau, fantasy, intricate binary and electronic designs, elegant, highly detailed, sharp focus, art by artgerm and greg rutkowski and wlop +symmetry!! portrait of a horizon zero dawn machine acting as ironman, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, 8 k +beautiful portrait of a minority female wearing fantastic costume,pigtail,intricate, elegant, highly detailed, dim volumetric lighting, 8k,octane,post-processing,digital painting, trending on artstation, concept art, smooth, sharp focus, illustration,by Tom Bagshaw and Daniel Gerhartz and Albert Aublet and Lawrence Alma-Tadema and alphonse mucha +a female elf sorceress by karol bak and jia ruan, beautiful detailed eyes, cute, fantasy, intricate, elegant, highly detailed, digital painting, 4 k, hdr, concept art, detailed jewelry, smooth, sharp focus, illustration, art by artgerm +high quality 3 d render very cute cyborg labrador!! dog plays drums!, cyberpunk highly detailed, unreal engine cinematic smooth, in the style of blade runner & pixar, hannah yata charlie immer, moody light, low angle, uhd 8 k, sharp focus +concept art of an intelligent bear, bipedal, wearing glasses and a vest, holding a spellbook under his arm, anthromorphic, artstation, fantasy +symmetrical - face!! portrait shot of evil sithlord captain kirk from star trek in star wars, realistic, professionally, professionally color graded, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +glorious full head portrait of abraham lincoln as Batman, fantasy, intricate, elegant, digital painting, trending on artstation, concept art, sharp focus, illustration by Gaston Bussiere and artgerm, 4k. +a photorealistic 3 d seamless pattern of honey material with macro closeup details of circuits cables nvidia motherboard pcb futuristic robotic elements in glass and mirror in the style of zaha hadid, 3 d realistic model render in cyberpunk 2 0 7 7 colors, unreal engine 5, keyshot, octane, artstation trending, ultra high detail, ultra realistic, cinematic, 8 k, 1 6 k, large realistic elements in style of nanospace michael menzelincev, in style of lee souder, in plastic, dark atmosphere, tilt shift, depth of field +skull - headed robot cyborg painting, illutstration, concept art, cyberpunk, futurism, comics art, artgerm +hyper realistic portrait, beautifully rendered, luis guzman as luigi wearing green, smirking deviously, painted by greg rutkowski, wlop, artgerm, dishonored 2 +mahindra thar driving through madagascar with baobabs trees, artgerm and greg rutkowski and alphonse mucha, an epic fantasy, volumetric light, detailed, establishing shot, an epic fantasy, trending on art station, octane render, midsommar +kurdish! assassins creed game set in kurdistan!, concept art, digital painting, highly detailed, 8 k, high definition +portrait of ((mischievous)), baleful young Cate Blanchett as young Galadriel as a queen of fairies, dressed in a beautiful silver dress. The background is a dark, creepy eastern europen forrest. night, horroristic shadows, high contrasts, lumnious, photorealistic, dreamlike, (mist filters), theatrical, character concept art by ruan jia, thomas kinkade, and J.Dickenson, trending on Artstation +elephant yoda playin socker, stunning digital art, high detail, in the style of artgerm, artstation, cgsociety, dramatic lighting, pixar 3d 8k +photo of nikolas cage as ken from street fighter 2, shoulder length hair, high - contrast, intricate, action pose, highly detailed, centered, digital painting, artstation, smooth, sharp focus, illustration, artgerm, tomasz alen kopera, peter mohrbacher, donato giancola, joseph christian leyendecker, wlop, boris vallejo +a portrait of frodo baggins, fantasy, sharp focus, intricate, elegant, digital painting, artstation, matte, highly detailed, concept art, illustration, ambient lighting, art by ilya kuvshinov, artgerm, alphonse mucha, and greg rutkowski +a very beautiful anime elf girl, full body, long silver hair with a flower, sky blue eyes, full round face, short smile, revealing clothes, thick thigs, firm chest, ice snowy lake setting, cinematic lightning, medium shot, mid-shot, highly detailed, trending on Artstation, Unreal Engine 4k, cinematic wallpaper by Stanley Artgerm Lau, WLOP, Rossdraws, James Jean, Andrei Riabovitchev, Marc Simonetti, and Sakimichan +a League of Legends FAN ART Portrait of VI, pink hair, short hair, elegant, highly detailed, digital painting, concept art, smooth, sharp focus, illustration, by Laurie Greasley,Lawrence Alma-Tadema,Dan Mumford,artstation,deviantart,Unreal Engine,face enhance,8K,golden ratio,cinematic lighting +!dream concept art, four glam rockers dressd as a mix of hooligans and whores, walking down a dark wet london alley at night, by ashley wood, by roger deakins, atmospheric +art portrait of death, 8 k, by tristan eaton, stanley artgermm, tom bagshaw, greg rutkowski, carne griffiths, trending on deviantart, face enhance, hyper detailed, minimalist cinematic lighting, trending on artstation, 4 k, hyperrealistic, focused, extreme details, unreal engine 5, cinematic, masterpiece, full of colour, +A beautiful robotic woman dreaming, cinematic lighting, soft bokeh, sci-fi, modern, colourful, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, by greg rutkowski +highly detailed vfx portrait of ichigo kurosaki from bleach by tite kubo!!!, stephen bliss, greg rutkowski, loish, rhads, beeple, makoto shinkai, tom bagshaw, alphonse mucha, sharp focus, art by artgerm and greg rutkowski, stanley kubrick, backlit!!, +fantasy city at night while giant ball of fire crashes to the ground, surreal, digital art, concept art, highly detailed, trending on artstation +concept art of a lightray trapped in vacuum, high definition, symmetrical, insanely detailed, elegant, intricate, hypermaximalist, cgsociety, prizewinning, trending on artstation, popular, top 1 0 0, best, winner, mentor, guru +a dream microphone in a dystopic world full of aberration, black & white, melting, webbing, 8 k, by tristan eaton, stanley artgerm, tom bagshaw, greg rutkowski, carne griffiths, ayami kojima, beksinski, giger, trending on deviantart, face enhance, hyper detailed, minimalist, horror, alien +link from zelda using computer, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, 8 k +lofi steampunk portrait pixar style by (((Lita Cabellut))) and Stanley Artgerm and Tom Bagshaw +fractional reserve banking, watercolor, trending on artstation +a painting of a tank getting shot at in world war 2 by Bernardo Bellotto, high detail, hyperrealistic, concept art, artstation, 8k +a beautiful diva sings on the theater stage , octane render, cgsociety, artstation trending, palatial scene, highly detailded +Ghibli, good day, landscape, no people, no man, fantasy, wood, vibrant world, Anime Background, concept art, illustration,smooth, sharp focus, intricate, super wide angle, trending on artstation, trending on deviantart, Hayao Miyazaki, 4K +sapphire viking warrior, regal, elegant, winter, snow, beautiful, stunning, hd, illustration, epic, d & d, fantasy, intricate, elegant, highly detailed, wide angle, digital painting, artstation, concept art, smooth, sharp focus, illustration, wallpaper, art by artgerm and greg rutkowski and alphonse mucha and jin xiaodi +fullbody!! dynamic action pose, beautiful woman with blue hair, antlers on her head, long flowing intricate black dress, dnd, face, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +A masterpiece ultrarealistic ultradetailed portrait of a Incredibly beautiful llama with dreadlocks IN INCREDIBLE GLASSES. baroque renaissance. in the forest. White corset. medium shot, intricate, elegant, highly detailed. trending on artstation, digital art, by Stanley Artgerm Lau, WLOP, Rossdraws, James Jean, Andrei Riabovitchev, Marc Simonetti, Yoshitaka Amano. background by James Jean and Gustav Klimt, light by Julie Bell, 4k, porcelain skin. BY ZDIZISLAW BEKSINSKI Cinematic concept art +young harry potter as a gepard with gepard skin patterns hyper detailed, digital art, trending on artstation, cinematic lighting +a dik dik monster with tattoos, wearing a fedora, tattoos, colorful, digital art, fantasy, magic, trending on artstation, ultra detailed, professional illustration by basil gogos +a astronaut walking on a alien planet with alien plants and looking to a alien breathtaking landscape, cinematic lighting, concept art, trending on Artstation, trending on DeviantArt, highly detailed, high quality, 8K HDR, octane render, unreal engine 5, breathtaking landscape, highly detailed, high quality, post processed +skeleton geisha in a burdel, Tending on artstation, concept art, dark colors, 8k +realistic attractive grungy woman with rainbow hair, drunk, angry, soft eyes and narrow chin, dainty figure, long hair straight down, torn overalls, basic white background, side boob, tattooed, pierced, flirty, wet shirt, wet, raining, highly detailed face, realistic face, beautiful detailed eyes, fantasy art, in the style of greg rutkowski, illustration, epic, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing, vibrant, +colossal orange viking royal king tabby cat, golden hour, fantasy, vivid colors, sharp focus, digital art, hyper - realistic, 4 k, unreal engine, highly detailed, hd, dramatic lighting by brom, trending on artstation +pencial drawing concept art of a machine mutant martial artist in the style of akira toriyama / hirohiko araki / tite kubo / masashi kishimoto trending on artstation deviantart pinterest detailed realistic hd 8 k high resolution +goth rainbow bright, fantasy, d & d, intricate, detailed, by by alphonse mucha, adolfo hohenstein, alice russell glenny, stanley artgerm lau, greg rutkowski, detailed, trending on artstation, trending on artstation, smooth +symmetry!! the eternal struggle of good and evil, very detailed, perfect lighting, perfect composition, 4 k, artstation, artgerm, derek zabrocki, greg rutkowski +portrait of beautiful cute young goth girl with glasses, cyberpunk, high details, neon, art by ( ( ( kuvshinov ilya ) ) ) and wayne barlowe and gustav klimt and artgerm and wlop and william - adolphe bouguereau +young Erin Gray as a ruggedly beautiful retro SCI-FI space heroine 1985 , intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and donato giancola and Joseph Christian Leyendecker, Ross Tran, WLOP +a large colorful candy cane is sticking out the ground on the side of a serene foot path. there are some snow drifts laying against the candy. there are snow flurries in the air. epic, awe inspiring, dramatic lighting, cinematic, extremely high detail, photorealistic, cinematic lighting, trending on artstation cgsociety rendered in unreal engine, 4 k, hq, +a hyper - detailed 3 d render like a oil painting of the construction of a upward spiral, surrealism!!!!! surreal concept art, lifelike, photorealistic, digital painting, aesthetic, smooth, sharp focus, artstation hd, by greg rutkowski, bruce pennington, valentina remenar and asher duran, +a octane render of a violent tornado inside a jar, close - up studio photo, studio lighting, path traced, highly detailed, high quality, hyperrealistic, concept art, digital art, trending on artstation, cinematic, high coherence, epic scene, 8 k hdr, high contrast +portrait of a young, ruggedly handsome ranger, muscular, half body, leather, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +the avengers fighting thanos, long shadow, warm colors, by Greg Rutkowski, artstation +a person looking like vladimir putin riding giant steel krab, masterpiece, intricate, elegant futuristic wardrobe, highly detailed, digital painting, artstation, concept art, crepuscular rays, smooth, sharp focus, illustration, background galaxy, cyberpunk colors, volumetric lighting, art by artgerm and james jean and nick sullo +book cover!!!!!!!!!!!!, old bridge, ivy vector elements at each border, fantasy forest landscape, fantasy magic, light night, intricate, elegant, sharp focus, illustration, highly detailed, digital painting, concept art, matte, art by wlop and artgerm and ivan shishkin and andrey shishkin, masterpiece +a modernist courtroom in the rainforest by raphael, hopper, and rene magritte. detailed, proportional, romantic, vibrant, enchanting, achingly beautiful, graphic print, trending on artstation, jungle, tropical, foliage, flowering, blooming +portrait of a beautiful mysterious woman holding a bouquet of flowing flowers, hair flowing upwards, small bubbles from her mouth, hands hidden under the bouquet, submerged underwater filled with colorful small fish and coral reef, fantasy, regal, intricate, by stanley artgerm lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman rockwell +portrait of a friendly charming formal barbarian giant noble!, imperial royal elegant clothing, elegant, rule of thirds, extremely detailed, artstation, concept art, matte, sharp focus, art by greg rutkowski, cover by artgerm +goldfinger, character sheet, concept design, contrast, kim jung gi, greg rutkowski, zabrocki, karlkka, jayison devadas, trending on artstation, 8 k, ultra wide angle, pincushion lens effect +a watercolor ink painting of scooby - doo as the primordial eldritch god of natural - disasters in the style of jean giraud in the style of moebius trending on artstation deviantart pinterest detailed realistic hd 8 k high resolution +zidane and shrek wearing vr playing gta v, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by greg rutkowski and alphonse mucha +full body portrait of marvel cinematic universe aaliyah haughton, she venom, spider man, elegant, webs, super hero, spider web background, highly detailed!! digital painting, artstation, glamor pose, concept art, sharp focus, illustration, art by artgerm and greg rutkowski, artey freytag +demonic evil cute fourteen year old brown skinned asian girl, tomboy, evil smile, freckles!!!, fully clothed, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, cinematic lighting, art by artgerm and greg rutkowski and alphonse mucha, +renfri, the princess turned gang leader from the witcher universe. fantasy art by greg rutkowski, gustave courbet, rosa bonheur, edward hopper. faithfully depicted facial expression, perfect anatomy, sharp focus, global illumination, radiant light, detailed and intricate environment, trending on artstation +A chef with a big mustache proundly making a soup, digital painting, artstation, concept art, Craig Mullins, Breathtaking, 8k resolution, extremely detailed, beautiful, establishing shot, artistic, hyperrealistic, octane render, cinematic lighting, dramatic lighting, masterpiece, light brazen, extremely detailed and beautiful face +a space realistic robot with big and cute eyes, | | very anime, fine - face, realistic shaded robotic parts, fine details. anime. realistic shaded lighting poster by ilya kuvshinov katsuhiro otomo ghost - in - the - shell, magali villeneuve, artgerm, jeremy lipkin and michael garmash, rob rey and kentaro miura style, trending on art station +Portrait of The Most Beautiful old Woman On Earth , D&D, fantasy, intricate, richly detailed colored 3D illustration of a beautiful ornated cute body with long metallic hair wearing a hoodie and short skirt that is happy and curious smile. background with completely rendered reflections, art by Range Murata and Artgerm highly detailed, digital painting, trending on artstation, sharp focus, illustration, style of Stanley Artgerm, perfect smile and sexy mouth, +lucifer cast out of heaven by yusuke murata and makoto shinkai, clouds, fire, angels, 8k, cel shaded, unreal engine, featured on artstation, pixiv +A pirate ship in the middle of the sea during a storm, fantasy art, in the style of greg rutkowski, illustration, epic, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing +fox as a monkey, fluffy white fur, black ears, stunning green eyes, extremely long white tail with black tip, award winning creature portrait photography, extremely detailed, artstation, 8 k, sensual lighting, incredible art, wlop, artgerm +autumn in french village, ornate, beautiful, atmosphere, vibe, mist, smoke, fire, chimney, rain, wet, pristine, puddles, melting, dripping, snow, creek, lush, ice, bridge, green, stained glass, forest, roses, flowers, by stanley artgerm lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, norman rockwell +Keanu Reeves as spiderman , film still, muscle extremely detailed, fantastic details full face, mouth, trending on artstation, pixiv, cgsociety, hyperdetailed Unreal Engine 4k 8k ultra HD, WLOP +symmetry!! portrait of elon musk with a salvador dali moustache intricate, neon lights, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski +Movie still of danny devito as as Harry Potter in potions class at hogwarts, fantasy, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by artgerm and Tony Sart +a planet that resembles a skull, stars in the background, natural, ultra detail. digital painting, beautiful, concept art, ethereal, cinematic, epic, 8k, highly detail, insane detailed, oil painting, octane render, cinematic lighting, smooth, sharp, Artstation, mystical, illustration, Trending on Artstation, Artstation HQ, Artstation HD, digital art, +anthropomorphic art of a businessman dragon, green dragon, dragon head, portrait, victorian inspired clothing by artgerm, victo ngai, ryohei hase, artstation. fractal papers and books. highly detailed digital painting, smooth, global illumination, fantasy art by greg rutkowsky, karl spitzweg +Billie Eilish, sitting in a cafe, fantasy, intricate, elegant, highly detailed, digital painting, pale skin, artstation, concept art, matte, sharp focus, illustration, art by Artgerm and Greg Rutkowski and Alphonse Mucha +a tornado made of fire on a field, au naturel, hyper detailed, digital art, trending in artstation, cinematic lighting, studio quality, smooth render, unreal engine 5 rendered, octane rendered, art style by klimt and nixeu and ian sprigger and wlop and krenz cushart +a queue of grey people looking like perfect copies of each other, in the style of artgerm, gerald brom, atey ghailan and mike mignola, vibrant colors and hard shadows and strong rim light, plain background, comic cover art, trending on artstation +vampire in the style of stefan kostic, realistic, full body shot, wide angle, sharp focus, 8 k high definition, insanely detailed, intricate, elegant, art by stanley lau and artgerm, floating embers +fluffy cat in cowboy hat like a tiny girl riding on the back of a giant corgi, by greg rutkowski +beautiful black woman elf wearing a dark green robe portrait, art nouveau, fantasy, intricate arcane wiccan designs, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, art by Artgerm and Greg Rutkowski and WLOP +portrait of a wizard, intricate, highly detailed, digital painting, artstation, concept art, sharp focus, art by huifeng huang and greg rutkowski +daredevil portrait, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and alphonse mucha +a highly detailed illustration of tall beautiful red haired lady wearing black spaghetti strap noir style dress and sun hat, elegant stroking face pose, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, league of legends concept art, wlop. +a hooded wise old man with a long white beard wearing a brown hooded tunic riding on top of a lion, the man riding is on the lion, the wise man is riding on top, he is all alone, majestic, epic digital art, cinematic, trending on artstation, superb detail 8 k, wide angle shot, masterpiece +a lisa frank mcdonalds microwaved happymeal, gothic, highly detailed, digital painting, artstation, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha and william - adolphe bouguereau +a beautiful highly detailed matte painting of a building looking like a goose by Jose Daniel Cabrera Pena and Leonid Kozienko, concept art by Tooth Wu and wlop and beeple and dan mumford and greg rutkowski and nekroxiii. octane render, cinematic, hyper realism, octane render, 8k, iridescent accents. vibrant, teal and gold blue red dark noir colour scheme +a statue made of red marble, of an beautiful girl, full body shot, perfect body, red white biomechanical, inflateble shapes, wearing epic bionic cyborg implants, masterpiece, intricate, biopunk futuristic wardrobe, vogue, highly detailed, artstation, concept art, background galaxy, cyberpunk, octane render +a ultradetailed beautiful panting of scarlett johansson as motoko kusanagi, by conrad roset, greg rutkowski and makoto shinkai, trending on artstation +a detailed portrait of a weasel assassin dressed with a leather armor, by justin gerard and greg rutkowski, digital art, realistic painting, dnd, character design, trending on artstation +a soldier zombie with a gas mask, pile of skulls, horror, black and white, fantasy art, monster art, illustration, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing +yvonne strahovski, very sexy penguin outfit, medium shot, visible face, detailed face, perfectly shaded, atmospheric lighting, by makoto shinkai, stanley artgerm lau, wlop, rossdraws +concept art of love, death + robots series of netflix, cinematic shot, oil painting by jama jurabaev, brush hard, artstation, for aaa game, high quality, brush stroke +Ottoman Emperor George Washington, diffuse lighting, fantasy, intricate, elegant, highly detailed, lifelike, photorealistic, digital painting, Ottoman armor, artstation, illustration, concept art, smooth, sharp focus, art by John Collier and Albert Aublet and Krenz Cushart and Artem Demura and Alphonse Mucha +Full potrait of cinead o'connor as an angel, hyper realistic, prismatic highlights, atmosphere, gorgeous, depth of field, cinematic, macro, concept art, 50mm, artstation, wlop, elegant, epic, weta digital, focus, octane render, v-ray, 8k, kodak portra, art by Liberatore +a film still of of a woman explorer, ( emerald herald ), exploring lost ruins, sun lighting, water, finely detailed features, perfect art, at an ancient city, gapmoe yandere grimdark, trending on pixiv fanbox, painted by greg rutkowski makoto shinkai takashi takeuchi studio ghibli,, akihiko yoshida +a very beautiful young yuuki asuna, full body, long wavy blond hair, sky blue eyes, full round face,, bikini, miniskirt, front view, mid - shot, highly detailed, cinematic wallpaper by stanley artgerm lau +symmetry!! portrait of jair bolsonaro, sci - fi, tech wear, glowing lights!! intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +an art and technology t - shirt, digital art from artstation, by ruan jia and mandy jurgens and artgerm and william - adolphe bouguereau fantasy, epic digital art, volumetrc lighting, clean detail 8 k resolution +gamora, portrait, digital painting, elegant, beautiful, highly detailed, artstation, concept art +a mechanized version of a norse woman, facial piercings, very symmetrical, furry warrior's bone clothing, highly detailed, by vitaly bulgarov, joss nizzi, ben procter, steve jung, concept art, concept art world, pinterest, artstation, unreal engine +photorealistic dwayne johnson but he is made of rocks. hyperdetailed photorealism, 1 0 8 megapixels, amazing depth, glowing rich colors, powerful imagery, 3 d finalrender, 3 d shading, cinematic lighting, artstation concept art +anime young boy with short wavy white hair wearing white clothes with short cape surrounded by light orbs, moody, wlop, concept art, digital painting, trending on artstation, highly detailed, epic composition, 8 k uhd +a photo of 8 k ultra realistic humanoid princess standing next to a beautiful view, ornate white and gold officers outfit, cinematic lighting, trending on artstation, 4 k, hyperrealistic, focused, extreme details, unreal engine 5, cinematic, masterpiece +epic 3 d abstract model, liquid headdress, 2 0 mm, with pastel pink and cerulean peanut butter, melting smoothly into other faces, liquid, delicate, beautiful, intricate, houdini sidefx, trending on artstation, by jeremy mann and ilya kuvshinov, jamie hewlett and ayami kojima +Idris Elba as Superman (2019), zac snyder, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +kneeling before a condescending queen, royal gown, golden detailing, medium shot, intricate, elegant, highly detailed, digital painting, volumetric light, artstation, concept art, smooth, sharp focus, illustration, art by Gil Elvgren and Greg Rutkowski and Alphonse Mucha, 8K +a highly detailed and high technology alien spacecraft, centered, corals, plume made of geometry, water texture, wet, wet lighting, extremly detailed digital painting, sharp focus in the style of android jones, artwork of a futuristic artificial intelligence superstar with frames made of detailed circuits, mystical colors, rim light, beautiful lighting, 8 k, stunning scene, raytracing, octane, under water visual distortion, dark tones colors, trending on artstation +metalhead, by yoshitaka amano, ruan jia, kentaro miura, artgerm, detailed, intricate details, trending on artstation, hd, masterpiece +futuristic utopian city, central hub, white buildings, golden sunset, space ships, green trees, large flying drones, utopia, high quality, hopeful, beautiful design, scifi, high detail, global illumination, trending on artstation, art by richard dumont, leon tukker +Fae teenage girl, portrait, face, long red hair, green highlights, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +charizard flying above new york, highly detailed matte fantasy painting, stormy lighting, by ross tran, by artgerm, by lisa frank, by brom, by peter mohrbacher +Glowing glass jar with a pink tentacle in green liquid, macro, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, volumetric lighting, cinematic, illustration, art by Artgerm and Greg Rutkowski and Alphonse Mucha +jazz music, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by wlop, mars ravelo and greg rutkowski +concept art of fried egg, highly detailed painting by dustin nguyen, akihiko yoshida, greg tocchini, greg rutkowski, cliff chiang, 4 k resolution, trending on artstation, 8 k +portrait of a rugged ranger, muscular, upper body, hairy torso, detailed detailed detailed hands hands hands hands, D&D, fantasy, bare bare bare bare thighs thighs thighs intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm +an extremely psychedelic portrait of hunter s. thompson, surreal, lsd, face, detailed, intricate, elegant, lithe, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration +greg manchess portrait painting of snufkin as overwatch character, medium shot, asymmetrical, profile picture, organic painting, nebula, matte painting, bold shapes, hard edges, street art, trending on artstation, by huang guangjian and gil elvgren and sachin teng +portrait of abandoned ribbed sculpture of two kissing cyborgs, covered with tentacles, roots, wires, tubes, ash, mold, baroque painting, standing in a desolate empty wasteland, creepy, nightmare, dream-like heavy atmosphere, dark fog, surreal abandoned buildings, baroque painting, beautiful detailed intricate insanely detailed octane render trending on Artstation, 8K artistic photography, photorealistic, volumetric cinematic light, chiaroscuro, zoomed out, Raphael, Caravaggio, Beksinski, Giger +underwater naga portrait, Pixar style, by Tristan Eaton Stanley Artgerm and Tom Bagshaw. +priestess with angelical wings, golden hair, fluorescent eyes, white skin, lipstick, beautiful, goodness, high fantasy, illustration, by artgerm, greg rutkowski, alphonse mucha +a warlock is casting a magic spell, with magic orb floating in his hand , dynamic pose, natural lighting, medium level shot, Mucha style , Grim fantasy, illustration ,concept art, +portrait of the secretive vampire woman biker loner smiling at her cat, by yoshitaka amano, casey baugh, steve caldwell, gottfried helnwein, yasunari ikenaga, nico tanigawa, and artgerm rendered with 3 d effect. +aliens in Jerusalem, concept art, hd +many Alchemy Imperial legends knights super hero boys girl, sci-fi, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, fractal flame, amazing composition unreal engine +concept art, of buffaloes on a beach, sunset, 30mm, canon, very hot, crowded, artstation +portrait of john candy crying, john candy suffering, metaverse on fire, octane render, trending on artstation +a portrait of a cyborg josip broz tito. vaporwave, intricate, epic lighting, cinematic composition, hyper realistic, 8 k resolution, unreal engine 5, by artgerm, tooth wu, dan mumford, beeple, wlop, rossdraws, james jean, marc simonetti, artstation +anime portrait of the priestess in the forest, enchanted, magic, digital, concept art, Kyoto animation,last exile, blue submarine no. 6, katsura masakazu,tsutomu nihei, gustav klimt,loish, murata range, kawaii, studio lighting, manga, bright colors, anime,beautiful, 35mm lens,noir, vibrant high contrast, gradation, jean giraud, moebius, fantasy, rule of thirds, unreal engine, fibonacci, intricate, cel shaded, blender npr, flat, matte print, smooth, Ilya Kuvshinov, Tsuruta Kenji +a large 1 8 th century pirate airship flying among the clouds, soaring through the sky, airship, digital art, pirate ship, vivid colors, artgerm, james gilleard, beautiful, highly detailed, intricate, trending on art station +Taylor Swift Cosplaying Lola Bunny, modeling, posing, two piece workout clothes, training bra, quality lighting, vibrant colors, maximalism, facial details, photograph of Taylor Swift, Tooth Wu Artgerm WLOP artstation deviantart, 8k, fanart, playboy style, very very aesthetic +a cartoon pineapple holding a large glass of port, nightclub, elegant, real life skin, intricate, high detailed, artstation, concept art, smooth, sharp focus, art by artgerm and greg rutkowski +powerful goddess of water clothed in swirling water striding through a stormy sea, dress made of water, highly detailed matte fantasy painting, rendered in octane, stormy lighting, by ross tran, by artgerm, by david suh, by peter mohrbacher +an extremely detailed matte painting emma watson as borg nine star trek, digital painting, beautiful eyes!, pretty face!!, symmetry, concept art, sharp focus, illustration, art by artgerm! greg rutkowski magali villeneuve wlop! ilya kuvshinov!!, octane render +bruce campbell as harry potter in “ harry potter and the philosopher's stone ” ( 2 0 0 1 ). movie still detailed, smooth, sharp focus. +a beautiful portrait of a tree goddess by Greg Rutkowski and Raymond Swanland, Trending on Artstation, ultra realistic digital art +a beautiful masterpiece painting of the last poet whispering,'if all can begin again, then everything must continue!'by juan gimenez, long shiny black hair blue eyes, award winning, trending on artstation, photorealistic, hyperrealism, octane render, unreal engine +cabin high on a mountain, the valley beneath, dynamic lighting, photorealistic fantasy concept art, trending on art station, stunning visuals, creative, cinematic, ultra detailed +a painting so beautiful and universally loved it creates peace on earth, profound epiphany, trending on artstation, by john singer sargent +Majestic powerfull red white Winged Hussars cavalry horde charging at ugly rainbow demons and trolls on ground, huge golden cross above them on the sky, white red eagle helping hussars, blood, snow, wide angle, professional kodak lenses, magic, fire, face painting, dramatic lighting, intricate, wild, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha, footage from space camera +bob ross!! riding a dinosaur, giant paintbrush in hand, model pose, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and alphonse mucha +Concept art of a Toilet-Plunger designed by Apple Inc +colorful medieval botanical garden, ornate, beautiful, atmosphere, vibe, mist, smoke, chimney, rain, well, wet, pristine, puddles, waterfall, melting, dripping, snow, ducks, creek, lush, ice, bridge, cart, forest, flowers, concept art illustration, color page, 4 k, tone mapping, akihiko yoshida, james jean, andrei riabovitchev, marc simonetti, yoshitaka amano, digital illustration, greg rutowski, volumetric lighting, sunbeams, particles, trending on artstation +a synthwave cuber bokeh brain, tristan eaton, victo ngai, artgerm, rhads, ross draws +portrait of korean beautiful female necromancer, face, dark fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +Intergalactic plant store floating in space with white twinkling stars in the foreground, galactic terrarium filled with plants from alien planets floating in the cosmos, Filled with plants, warm ethereal glowing ambiance, concept art 8k resolution +portrait of a friendly charming formal barbarian giant noble!, imperial royal elegant clothing, elegant, rule of thirds, extremely detailed, artstation, concept art, matte, sharp focus, art by greg rutkowski, cover by artgerm +artgerm, joshua middleton comic cover art, full body pretty even rachel wood faye, symmetrical eyes, symmetrical face, long curly black hair, beautiful forest, cinematic lighting +a forgotten garden gnome in a vast barren desert, hopeless wasteland background with a relentless raging sun overhead, an ultrafine detailed painting by stanley artgerm lau, greg rutkowski, thomas kindkade, alphonse mucha, loish, trending on deviantart, pop surrealism, whimsical, lowbrow, perfect symmetrical face, grotesque +Portrait of a tall beautiful brown-skin elf woman wearing stylish black and gold robes, warm smile, intricate, elegant, highly detailed, digital painting, smooth, sharp focus, artstation, graphic novel, art by stanley artgerm and greg rutkowski and peter mohrbacher, +a giant broken robots in rain after a huge battle, tired, rustic, dormant, sharp focus, james gilleard, cinematic, game art, extremely detailed digital painting, print +biolevel 4 secret lab, alien autopsy, wide angle, super highly detailed, professional digital painting, artstation, concept art, smooth, sharp focus, no blur, no dof, extreme illustration, unreal engine 5, photorealism, hd quality, 8 k resolution, cinema 4 d, 3 d, beautiful, cinematic, art by artgerm and greg rutkowski and alphonse mucha and loish and wlop +Gertrude Abercrombie, minimalistic graffiti masterpiece, minimalism, 3d abstract render overlayed, black background, psychedelic therapy, trending on ArtStation, ink splatters, pen lines, incredible detail, creative, positive energy, happy, unique, negative space, face, artgerm +a dramatic, epic, ethereal painting of a !!handsome!! thicc chunky beefy mischievous shirtless man with a big beer belly wearing a large belt and cowboy hat offering a whiskey bottle | he is relaxing by a campfire | background is a late night with food and jugs of whisky | homoerotic | stars, tarot card, art deco, art nouveau, intricate | by Mark Maggiori (((and Alphonse Mucha))) | trending on artstation +: sphere sculpture covered with maze pattern,hyper detailed art station parabolic lighting contest winners unrealengine trending on artstation,cinematic, hyper realism, high detail, octane render, 8k +starfinder lashunta pilot, wearing a flight suit, in a space port, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, art by artgerm and greg rutkowski +concept art of a futuristic gold warrior, large gold apendages on it's back, with a black obsidian helmet, tight armor, rough and jagged design | | epic - fine - fine details by stanley artgerm lau, wlop, rossdraws, and sakimichan, trending on artstation, brush strokes +a study of cell shaded cartoon of a monk on a skateboard with technical analysis charts in the background, illustration, wide shot, subtle colors, post grunge, concept art by josan gonzales and wlop, by james jean, Victo ngai, David Rubín, Mike Mignola, Laurie Greasley, highly detailed, sharp focus, alien, Trending on Artstation, HQ, deviantart, art by artgem +potato house interior design, Greg Rutkowski, trending on Artstation, 8K, ultra wide angle, pincushion lens effect. +a angry knight in full plate of black armor, splattered with blood, riding a large black war horse, with red glowing eyes flowing red mane and tail, blackened clouds cover sky, crackling with lightning, a castle in distance burns, concept art by greg rutkowski, craig mullins, todd mcfarlane, +sexy painting of 3 5 0 - pound taylor swift, red bikini, navel piercing, ultra realistic, sharp details, subsurface scattering, intricate details, warm lighting, beautiful features, highly detailed, photorealistic, octane render, 8 k, unreal engine, art by artgerm and greg rutkowski and alphonse mucha +the grand canyon filled with glowing futuristic cyberpunk skyscrapers at night with a starry sky, cinematic, wide angle establishing shot, fantasy, hyperrealism, greg rutkowski, tuomas korpi, volumetric light, octane render, photorealistic concept art, highly detailed, very intricate +Dwight Shrute as blue man. digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and donato giancola and Joseph Christian Leyendecker, Ross Tran, WLOP +death, dark fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, wallpaper, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +a realistic illustration portrait of a beautiful cute girl with wavy black red hair, a pointy nose and, round chin black eyeliner, green pupills, trending on artstation, hyper - realistic lighting, intricate by imagineartforyou +looking out to see a long wood dock on the water, child at end of dock, big fishing boat leaving the dock with sailors waving, low angle, long lens, sunset, a mediterranean phoenician fishing village in the distance, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and raphael lacoste and magali villeneuve +splash art for new champion for league of legend, by riot games. trending on artstation +Anime as Elizabeth Olsen playing Scarlet Witch || cute-fine-face, pretty face, realistic shaded Perfect face, fine details. Anime. realistic shaded lighting poster by Ilya Kuvshinov katsuhiro otomo ghost-in-the-shell, magali villeneuve, artgerm, Jeremy Lipkin and Michael Garmash and Rob Rey as Scarlet Witch in New York cute smile +a portrait of apocalypse from x - men, fantasy, sharp focus, intricate, elegant, digital painting, artstation, matte, highly detailed, concept art, illustration, ambient lighting, art by ilya kuvshinov, artgerm, alphonse mucha, and greg rutkowski +portrait of radical lolita girl, dreamy and ethereal and dark, dark eyes, smiling expression, ornate goth dress, dark fantasy, chaotic, elegant, black crows flying, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +photography of man playing realistic virtual reality game in a giant mine with excavators and gnomes 3 d realistic model render in the style of zaha hadid with point cloud in the middle, in cyberpunk 2 0 7 7 colors, unreal engine 5, keyshot, octane, artstation trending, ultra high detail, ultra realistic, cinematic, 8 k, 1 6 k, in style of zaha hadid, in style of nanospace michael menzelincev, in style of lee souder, in plastic, dark atmosphere, tilt shift, depth of field +greg manchess portrait painting of armored starlord as overwatch character, medium shot, asymmetrical, profile picture, organic painting, sunny day, matte painting, bold shapes, hard edges, street art, trending on artstation, by huang guangjian and gil elvgren and sachin teng +full lenght shot, super hero pose, biomechanical dress, inflateble shapes, wearing epic bionic cyborg implants, masterpiece, intricate, biopunk futuristic wardrobe, highly detailed, art by akira, mike mignola, artstation, concept art, background galaxy, cyberpunk, octane render +alterd carbon, masked angel protecting girl and a woman, vampre the masquerade, neon, detailed intricate render, dark atmosphere, detailed illustration, hd, 4 k, digital art, overdetailed art, surrealistic, by greg rutkowski, by loish, complementing colors, trending on artstation, deviantart +fullbody portrait of a beautiful girl dressed in cyberpunk style, standing on street, holding a sniper rifle. by riot games, anime style, masterpiece, award - winning, trending on artstation and pixiv +thief red riding hood, d & d, fantasy, portrait, highly detailed, digital painting, trending on artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and magali villeneuve +anthropomorphic highly detailed group portrait of funny neon giant cute eyes dust mephit, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm, bob eggleton, michael whelan, stephen hickman, richard corben, wayne barlowe, trending on artstation and greg rutkowski and alphonse mucha, 8 k +beautiful girl galaxy background, portrait character concept style trending on artstation concept art detailed octane render cinematic photo - realistic 8 k high detailed +realistic render of flying blue whales towards the moon, intricate, toy, sci - fi, extremely detailed, digital painting, sculpted in zbrush, artstation, concept art, smooth, sharp focus, illustration, chiaroscuro lighting, golden ratio, incredible art by artgerm and greg rutkowski and alphonse mucha and simon stalenhag +a highly detailed epic cinematic concept art CG render digital painting artwork: Steampunk Wizard stands and looks at the Tower of Babel in the distance. By Greg Rutkowski, in the style of Francis Bacon and Syd Mead and Norman Rockwell and Beksinski, open ceiling, highly detailed, painted by Francis Bacon and Edward Hopper, painted by James Gilleard, surrealism, airbrush, Ilya Kuvshinov, WLOP, Stanley Artgerm, very coherent, triadic color scheme, art by Takato Yamamoto and James Jean +devastated scorched earth in the valley, burnt trees, burnt vegetation and grass, cinematic view, epic sky, detailed, concept art, low angle, high detail, warm lighting, volumetric, godrays, vivid, beautiful, trending on artstation, by jordan grimmer, huge scene, grass, art greg rutkowski +portrait Ninja gaiden girl, armored black and red ninja wardrobe, in ruin japanese rainny temple night, ssci-fi and fantasy, intricate and very very beautiful and elegant, highly detailed, digital painting, artstation, concept art, smooth and sharp focus, illustration, art by tian zi and WLOP and alphonse mucha +girl jumping near a lake, rainy, touching a long neck monster, illustration concept art anime key visual trending pixiv fanbox by wlop and greg rutkowski and makoto shinkai and studio ghibli +beautiful digital painting of a hoyeon jung stylish female snow - covered mountains with high detail, real life skin, freckles, 8 k, stunning detail, works by artgerm, greg rutkowski and alphonse mucha, unreal engine 5, 4 k uhd +a potrait of a human rogue, fine details. night setting. realistic shaded lighting poster by ilya kuvshinov katsuhiro, artgerm, jeremy lipkin and michael garmash, unreal engine, radiant light, detailed and intricate environment, digital art, trending on art station +portrait full body girl 3 kingdom breathtaking detailed concept art painting art deco pattern of birds goddesses amalmation flowers head thibetan temple, by hsiao ron cheng, tetsuya ichida, bizarre compositions, tsutomu nihei, exquisite detail, extremely moody lighting, 8 k, art nouveau, old chines painting, art nouveau +greg manchess portrait painting of armored sanguinius with huge wings as overwatch character, medium shot, asymmetrical, profile picture, organic painting, sunny day, matte painting, bold shapes, hard edges, street art, trending on artstation, by huang guangjian and gil elvgren and sachin teng +rendering of old hands reaching forward, concept art, high detail, intimidating, cinematic, Artstation trending, octane render +dynamic photography portrait of a dungeons and dragons king's colosse , intricate ornate armor, subject in the middle of the frame, rule of thirds, golden ratio, elegant, digital painting, octane 4k render, zbrush, hyperrealistic, artstation, concept art, smooth, sharp focus, illustration from Warcraft by Ruan Jia and Mandy Jurgens and Artgerm and William-Adolphe Bouguerea +anthropomorphic triangle brain in edgy darkiron badger demon, intricate, elegant, highly detailed animal monster, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm, dwayne barlowe, trending on artstation and greg rutkowski and alphonse mucha, 8 k +christiano ronaldo, manga cover art, detailed color portrait, artstation trending, 8 k, greg rutkowski +a faceless!!!!! woman posing for the camera, charcoal painting!!!!! illustrated by kathe kollwitz, trending on artstation, 4 k, 8 k, artstation hd, artstation hq, artistic interpretation, 1 9 5 0 s style +a potrait of a female necromancer with big and cute eyes, fine - face, realistic shaded perfect face, fine details. night setting. very anime style. realistic shaded lighting poster by ilya kuvshinov katsuhiro, magali villeneuve, artgerm, jeremy lipkin and michael garmash, rob rey and kentaro miura style, trending on art station +fractal tarot card of a naturepunk retrofuture nexus of technology and earth, beautiful detailed realistic cinematic character high concept fashion portrait, hi - fructose art magazine, by anton fadeev and paul lehr and david heskin and josan gonzalez, 8 k +realistic high key portrait rendering of a beautiful curvy pale alabaster goth girl with asymmetrical punk rock hair and badass euro design sunglasses. mole on cheek. half portrait by stanley artgerm, dramatic lighting, by tohuvabohu, nagel, shin jeongho, nick silva and ilya kuvshinov, deviantart, detailed character design, 8 k resolution +the world serpent ultra detailed fantasy, elden ring, realistic, dnd character portrait, full body, dnd, rpg, lotr game design fanart by concept art, behance hd, artstation, deviantart, global illumination radiating a glowing aura global illumination ray tracing hdr render in unreal engine 5 +helmet of a forgotten deity with a labyrinth, in the style of tomasz alen kopera and fenghua zhong and peter mohrbacher, mystical colors, rim light, beautiful lighting, 8 k, stunning scene, raytracing, octane, trending on artstation +duotone psychedelic concept illustration 3 / 4 portrait of dr. albert hofmannn taking bicycle trip fractals background. cinematic scene. vlumetric lighting. golden rario accidental renaissance. by sachin teng and sergey kolesov and ruan jia and heng z. graffiti art, scifi, fantasy, hyper detailed. octane render. concept art. trending on artstation +A very tall, slender woman wearing black puffy clothes and holding a yellow umbrella, sharp focus, intricate, elegant, digital painting, artstation, matte, highly detailed, concept art, illustration, ambient lighting, art by artgerm, Alphonse mucha, and Greg Rutkowski +vibrant! colorful!!! the last supper of simpsons by rene magritte, futurama by laurie greasley and bouguereau, ( ( etching by gustave dore ) ), ultraclear intricate, sharp focus, highly detailed digital painting illustration, concept art, masterpiece +award winning brandmark for a research lab, mind wandering, hip corporate, no text, trendy, vector art, concept art +an epic non - binary model, subject made of white mesh rope, with cerulean and pastel pink bubbles bursting out, delicate, beautiful, intricate, melting into a wolf, houdini sidefx, by jeremy mann and ilya kuvshinov, jamie hewlett and ayami kojima, trending on artstation, bold 3 d +character concept of iridescent sinewy smooth muscular male sleek glossy indigo black pearlescent scifi armor with smooth black onyx featureless helmet, by greg rutkowski, mark brookes, jim burns, tom bagshaw, magali villeneuve, trending on artstation +phil noto, peter mohrbacher, thomas kinkade, artgerm, 1 9 5 0 s rockabilly anya taylor - joy catwoman dc comics, pompadour, long hair, vines, symmetrical eyes, city rooftop +dark high detailed space station interior a statue jesus on cross made of white marble, perfect symmetrical body, full body shot, inflateble shapes, wires, tubes, veins, jellyfish, white biomechanical details, wearing epic bionic cyborg implants, masterpiece, intricate, biopunk, vogue, highly detailed, artstation, concept art, cyberpunk, octane render +Portrait of a man by Greg Rutkowski, a young, strong and hard-eyed futuristic warrior with brown hair with dreadlocks, wearing a futuristic space tactical gear that looks like a mix between the samurai, viking and templar aesthetics, mix between tribal and hi-tech, highly detailed portrait, scifi, space opera, digital painting, artstation, concept art, smooth, sharp foccus ilustration, Artstation HQ +epic professional digital art of a snail in a blue professional business suit, sitting at a desk, best on artstation, cgsociety, wlop, Behance, pixiv, astonishing, impressive, outstanding, epic, cinematic, stunning, gorgeous, much detail, much wow, masterpiece +hyper realistic oil painting of frozen little island planet with waterfall, rising in the air, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +digitalart scifi!!! wallpaper trending on artstation +concept art of car designed by jony ive, jama jurabaev, science fiction, brush hard, artstation, cgsociety, high quality, brush stroke +A beautiful oil cartoony painting of a happy Remi Malek riding a tricycle by Lucas Graciano, Frank Frazetta, Greg Rutkowski, Boris Vallejo, epic fantasy character art, high fantasy, Exquisite detail, post-processing, low angle, masterpiece, cinematic +female priest in white cloak, ultra detailed fantasy, dndbeyond, bright, colourful, realistic, dnd character portrait, full body, pathfinder, pinterest, art by ralph horsley, dnd, rpg, lotr game design fanart by concept art, behance hd, artstation, deviantart, hdr render in unreal engine 5 +a beautiful portrait of death goddess by Greg Rutkowski and Raymond Swanland, ominous background, Trending on Artstation, ultra realistic digital art +cyborg drug addict, diffuse lighting, fantasy, intricate, elegant, highly detailed, lifelike, photorealistic, digital painting, artstation, illustration, concept art, smooth, sharp focus, art by John Collier and Albert Aublet and Krenz Cushart and Artem Demura and Alphonse Mucha +a well designed portrait of viper, detailed, realistic, sketch style, artstation, greg rutkowski, 8 k resolution. +Moira Stewart as Warhammer 40k Battle Sister, portrait, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +hu tao from genshin impact, hu tao, perfect face, collaborative painting by greg ruthowski, ruan jia, artgerm, highly detailed, complex, exquisite and beautiful, 4 k, 8 k, artstation +rockstar girl playing electric guitar on stage. by amano yoshitaka, by rembrandt, digital art, digital painting, artstation trending, unreal engine +beautiful small cyberpunk robot-owl in the deep jungle, with neon color eyes, cinematic view, 8k, ultra realistic, vibrant colors, photo realism, trending artstation, octane render, volumetric lighting, high contrast, intricate, highly detailed, digital painting +john lennon as jack the ripper, ultra realistic, concept art, intricate details, highly detailed, photorealistic, octane render, 8 k, unreal engine, art by frank frazetta, simon bisley, brom +lux, from league of legends, au naturel, hyper detailed, digital art, trending in artstation, cinematic lighting, studio quality, smooth render, unreal engine 5 rendered, octane rendered, art style by klimt and nixeu and ian sprigger and wlop and krenz cushart +Anime art of beautiful Hatsune miku with beautifel legs by artgerm, ross tran, magali villeneuve, Greg Rutkowski, Gil Elvgren, Alberto Vargas, Earl Moran,, Art Frahm, Enoch Bolles +Daniel Radcliffe wearing a monks tunic holding a glowing fire magical staff. Trending on Artstation, octane render, ultra detailed, art by Ross tran +an super mega hyper realistic image of a super soldier with a Ukrainian blue and yellow stripes flag standing in the beam of light from the clouds on a pile of skulls as a winner, masculine figure, D&D, fantasy, intricate, elegant, highly detailed, extremely detailed, digital painting, artstation, concept art, matte, sharp focus, symmetrical, illustration, art by Artgerm and Greg Rutkowski and Alphonse Mucha +poster woman with futuristic streetwear and hairstyle, open jacket, cute face, symmetrical face, 3/4 angle, pretty, beautiful, elegant, Anime by Kuvshinov Ilya, Cushart Krentz and Gilleard James, 4k, HDR, Trending on artstation, Behance, Pinterest +man with fluffy pipidastr, atmosphere, glow, detailed, intricate, full of colour, cinematic lighting, trending on artstation, 4 k, hyperrealistic, focused, extreme details, unreal engine 5, cinematic, masterpiece, moody lighting, by greg rutkowski, wlop, artgerm, trending on artstation, concept art, sharp focus, ray tracing +a cinematic scene from the cthulhu in pyrrhic victory, concept art by beksinski and jean delville, dramatic lighting, ultra hd, hdr, 8 k +indistinct glowing prehistoric beasts surrounded by slate grey walls, insane details, dramatic lighting, unreal engine 5, concept art, greg rutkowski, james gurney, johannes voss, hasui kawase. +a full body portrait of a beautiful post apocalyptic offworld nordic desert snake charmer dancing playfully by the waterfalls, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by krenz cushart and artem demura and alphonse mucha +dia de los muertos theme poster art by artemio rodriguez, aida muluneh, and gustave bauman, intricate, accurate facial details, profile picture, artgerm, retro, nostalgic, old fashioned, posterized color +digital artwork, illustration, cinematic camera, a cyborg pilot in the cockpit of a mech, intricate machinery, biomechanics, the ghosts in the machine, cyberpunk concept art by artgerm and Guy Denning and Greg Rutkowski and Ruan Jia, highly detailed, intricate, sci-fi, sharp focus, Trending on Artstation HQ, deviantart +breathtaking detailed soft painting of a grim reaper with an intricate golden scythe and cloak of fireflies and embers, rembrandt style, detailed art nouveau stained glass of flames background, christian saint rosace, elegant, highly detailed, artstation, concept art, matte, sharp focus, art by Tom Bagshaw, Artgerm and Greg Rutkowski +portrait of mischievous, enigmatic!!, dangerous youngster Galadriel (Cate Blanchett) as a queen of elves, dressed in a refined silvery garment. The background is a dark, chilling eastern european forrest. night, horroristic shadows, blue tones, higher contrasts, (((lumnious))), theatrical, character concept art by ruan jia, (((thomas kinkade))), and J.Dickenson, trending on Pinterest, ArtStation +portrait painting of a bloodied serial killer wearing a hello kitty mask, ultra realistic, concept art, intricate details, eerie, highly detailed, photorealistic, octane render, 8 k, unreal engine. art by artgerm and greg rutkowski and alphonse mucha +An old man trapped in a cave, looking into a mirror, b&w, fantasy art, in the style of masami kurumada, illustration, epic, fantasy, intricate, hyper detailed, artstation, concept art, smooth, sharp focus, ray tracing +close-up macro portrait of the face of a beautiful princess with animal skull mask, epic angle and pose, ribcage bones symmetrical artwork, 3d with depth of field, blurred background, cybernetic jellyfish female face skull phoenix bird, translucent, nautilus, energy flows of water and fire. a highly detailed epic cinematic concept art CG render. made in Maya, Blender and Photoshop, octane render, excellent composition, cinematic dystopian brutalist atmosphere, dynamic dramatic cinematic lighting, aesthetic, very inspirational, arthouse. y Greg Rutkowski, Ilya Kuvshinov, WLOP, Stanley Artgerm Lau, Ruan Jia and Fenghua Zhong +poseidon humanoid god of the sea, trident, highly detailed, d & d, fantasy, highly detailed, digital painting, trending on artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and magali villeneuve +grainy and distorted xerox of a classified scientific government chart diagram a portal to a higher dimension photorealistic 4k photorealism realistic textures sharpened x-files fringe mystery sci-fi cinematic detailed texture hyperdetailed CIA agency NSA DOD government seal redacted continuous feed paper smooth, sharp focus, illustration, from Metal Gear, Greg Rutkowski and Artgerm artgerm +martin shkreli in attack on titan, medium shot close up, details, sharp focus, illustration, by jordan grimmer and greg rutkowski, trending artstation, pixiv, digital art +painting Daft Punk in long coat, elegant, intricate, headshot, highly detailed, digital painting, artstation, concept art, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +colleen moore 2 2 years old, bob haircut, portrait painted by stanley artgerm, casting long shadows, resting head on hands, by ross tran +hyper realistic photography of a stunningly beautiful sphere, self assembly, ribbons, glowing consciences, growing tendrils, hand in the style of beth cavener, jin kagetsu,, and wlop, highly detailed, intricate filigree, symmetry, masterpiece, award winning, sharp focus, concept art, highkey lighting, ambient lighting, octane render, 8 k, artstation +a photo of larry david playing poker while smoking highly detailed, dim volumetric lighting, 8k, post-processing, soft painting, trending on artstation, concept art, smooth, sharp focus, illustration,by Tom Bagshaw and Daniel Gerhartz and Albert Aublet and Lawrence Alma-Tadema and alphonse mucha +symmetry!! portrait of space soldier, tech wear, scifi, glowing lights!! intricate elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by artgerm and greg rutkowski and alphonse mucha +portrait of female android, intricate, elegant, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by fra angelico +jason fused with a poltergeist lovecraft nervous space demon, spiky skin, creepy, melting, big eyes, photo, portrait, 3 d, high details, intricate details, by vincent di fate, artgerm julie bell beeple, 9 0 s, smooth gradients, volumetric lightning, high contrast, duo tone, depth of field, very coherent symmetrical artwork +portrait of a fat blue alien. big friendly smile. character concept art. science fiction illustration. close up of the face. key panel art graphic novel. detailed face, beautiful colour palette. digital painting. +people with posters attacking cops in front a huge blue spiral - shaped white luminous attractor that is floating on the horizon near the sun and stores in los angeles with light screens all over the street, concept art, art for the game, professional lighting, dark night lighting from streetlights +Lofi cyberpunk portrait beautiful woman with short brown curly hair, roman face, Romanesque, unicorn, rainbow, floral, Pixar style, Tristan Eaton, Stanley Artgerm, Tom Bagshaw +dog eat dog world , made by Stanley Artgerm Lau, WLOP, Rossdraws, ArtStation, CGSociety, concept art, cgsociety, octane render, trending on artstation, artstationHD, artstationHQ, unreal engine, 4k, 8k, diff --git a/demo/Diffusion/calibration.py b/demo/Diffusion/calibration.py new file mode 100644 index 00000000..98adb6d3 --- /dev/null +++ b/demo/Diffusion/calibration.py @@ -0,0 +1,177 @@ +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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 types +from typing import Callable, Optional, Union + +import numpy as np +import torch +import torch.distributed as dist +import torch.nn as nn +from torch.distributed import ReduceOp +from utilities import PercentileAmaxes + +from ammo.torch.quantization.model_calib import ( + enable_stats_collection, + finish_stats_collection, + max_calibrate, +) +from ammo.torch.quantization.utils import is_quantized_linear + + +def precentile_calib_mode(base_unet, quant_config={}): + def compute_amax(self, all_reduce=True): + """Return the absolute max of all tensors collected.""" + if ( + self._calib_amax is not None + and all_reduce + and dist.is_available() + and dist.is_initialized() + and dist.get_world_size() > 1 + ): + tmp_amax = self._calib_amax.clone() + dist.all_reduce(tmp_amax, op=ReduceOp.MAX) + self._calib_amax.copy_(tmp_amax) + if self._track_amax: + up_lim = int(self._amaxs.total_step * self._amaxs.percentile) + if up_lim <= 0: + up_lim = 1 + amaxs_values = [self._amaxs.data[i] for i in range(0, up_lim)] + act_amax = ( + torch.tensor(np.vstack(amaxs_values).min(axis=0)) + .float() + .squeeze(0) + .to(self._calib_amax.device) + .to(self._calib_amax.dtype) + ) + return act_amax + return self._calib_amax + + for _, module in base_unet.named_modules(): + if isinstance(module, (nn.Linear, nn.Conv2d)): + module.input_quantizer._calibrator._track_amax = True + module.input_quantizer._calibrator._amaxs = PercentileAmaxes( + total_step=quant_config["base-step"], percentile=quant_config["percentile"] + ) + module.input_quantizer._calibrator.compute_amax = types.MethodType( + compute_amax, module.input_quantizer._calibrator + ) + + +@torch.no_grad() +def smoothquant(model, forward_loop=None): + """ + Rewrite the original SmoothQuant method + """ + assert forward_loop is not None, "forward_loop must be provided for smoothquant" + max_calibrate(model, forward_loop) + + smoothed_modules = 0 + for name, module in model.named_modules(): + if is_quantized_linear(module): + if not hasattr(module.input_quantizer, "_amax"): + print(f"Warning: {name} is not calibrated, skip smoothing") + continue + if module.input_quantizer.num_bits != 8 or module.weight_quantizer.num_bits != 8: + print(f"Warning: only int8 smoothing is supported, skip {name}") + continue + if module.input_quantizer.axis != -1: + print(f"Warning: only per-channel smoothing is supported, skip {name}") + continue + + alpha = 1.0 + if hasattr(module, "alpha"): + alpha = module.alpha + assert ( + module.input_quantizer._amax.numel() > 1 + ), f"Error: {name} has only one channel to smooth" + + # It is important to keep scaling math in fp32 to be numerically safe + act_amax = module.input_quantizer.amax.float() + + act_device = act_amax.device + + # If model is split across devices, this tensor may be on wrong one + act_amax = act_amax.to(module.weight.device) + + weight_scale = module.weight.abs().max(dim=0, keepdim=True)[0] + scale_a = (weight_scale.pow(1 - alpha) / act_amax.pow(alpha)).squeeze() + + # Some channel could have 0 amax which causes scale_a to overflow. Explicitly mask them out here + epsilon = 1.0 / (1 << 31) + if act_amax.min() <= epsilon: + zero_mask = act_amax <= epsilon + scale_a[zero_mask] = 1 + inv_scale_a = 1.0 / scale_a + inv_scale_a = inv_scale_a.squeeze()[None, :] + + # Use per-tensor quantization for activation, add a pre-quantization scale vector + module.input_quantizer.pre_quant_scale = scale_a.to(module.weight.dtype).to(act_device) + module.input_quantizer._axis = None + delattr(module.input_quantizer, "_amax") + module.input_quantizer.amax = torch.tensor( + (act_amax * scale_a).max().item(), + dtype=module.weight.dtype, + device=module.weight.device, + ) + + # Multiply weight by inv_scale_a and recalibrate + module.weight.detach().copy_( + (module.weight.float() * inv_scale_a).to(module.weight.dtype) + ) + + enable_stats_collection(module.weight_quantizer) + module.weight_quantizer(module.weight) + finish_stats_collection(module.weight_quantizer) + + smoothed_modules += 1 + print(f"Smoothed {smoothed_modules} modules") + + +def calibrate( + model: nn.Module, + algorithm: Union[str, dict, None] = "max", + forward_loop: Optional[Callable] = None, +) -> None: + if algorithm is None: + return + + if isinstance(algorithm, str): + kwargs = {} + elif isinstance(algorithm, dict): + kwargs = algorithm.copy() + algorithm = kwargs.pop("method") + else: + raise TypeError(f"Unsupported type for algorithm: {type(algorithm)}") + + if algorithm == "smoothquant": + smoothquant(model, forward_loop) + elif algorithm == "max": + max_calibrate(model, forward_loop) + else: + raise ValueError(f"Unsupported calibration algorithm: {algorithm}") + + +def reg_alpha_qkv(base_unet, alpha): + """ + Only apply alpha to QKV layers + """ + for name, module in base_unet.named_modules(): + if isinstance(module, torch.nn.Linear): + if "to_q" in name or "to_k" in name or "to_v" in name: + module.alpha = alpha + diff --git a/demo/Diffusion/models.py b/demo/Diffusion/models.py index ffeb6cfd..ed9de4b0 100644 --- a/demo/Diffusion/models.py +++ b/demo/Diffusion/models.py @@ -15,6 +15,7 @@ # limitations under the License. # +from diffusers import DiffusionPipeline from diffusers.loaders import LoraLoaderMixin from diffusers.models import ( AutoencoderKL, @@ -29,6 +30,7 @@ import onnx_graphsurgeon as gs import os from polygraphy.backend.onnx.loader import fold_constants +import re import tempfile import torch import torch.nn.functional as F @@ -54,8 +56,7 @@ def info(self, prefix): def cleanup(self, return_onnx=False): self.graph.cleanup().toposort() - if return_onnx: - return gs.export_onnx(self.graph) + return gs.export_onnx(self.graph) if return_onnx else self.graph def select_outputs(self, keep, names=None): self.graph.outputs = [self.graph.outputs[o] for o in keep] @@ -108,6 +109,59 @@ def clip_add_hidden_states(self, return_onnx=False): if return_onnx: return onnx_graph + def fuse_mha_qkv_int8_sq(self): + tensors = self.graph.tensors() + keys = tensors.keys() + + # mha : fuse QKV QDQ nodes + # mhca : fuse KV QDQ nodes + q_pat = ( + "/down_blocks.\\d+/attentions.\\d+/transformer_blocks" + ".\\d+/attn\\d+/to_q/input_quantizer/DequantizeLinear_output_0" + ) + k_pat = ( + "/down_blocks.\\d+/attentions.\\d+/transformer_blocks" + ".\\d+/attn\\d+/to_k/input_quantizer/DequantizeLinear_output_0" + ) + v_pat = ( + "/down_blocks.\\d+/attentions.\\d+/transformer_blocks" + ".\\d+/attn\\d+/to_v/input_quantizer/DequantizeLinear_output_0" + ) + + qs = list(sorted(map( + lambda x: x.group(0), # type: ignore + filter(lambda x: x is not None, [re.match(q_pat, key) for key in keys]), + ))) + ks = list(sorted(map( + lambda x: x.group(0), # type: ignore + filter(lambda x: x is not None, [re.match(k_pat, key) for key in keys]), + ))) + vs = list(sorted(map( + lambda x: x.group(0), # type: ignore + filter(lambda x: x is not None, [re.match(v_pat, key) for key in keys]), + ))) + + removed = 0 + assert len(qs) == len(ks) == len(vs), "Failed to collect tensors" + for q, k, v in zip(qs, ks, vs): + is_mha = all(["attn1" in tensor for tensor in [q, k, v]]) + is_mhca = all(["attn2" in tensor for tensor in [q, k, v]]) + assert (is_mha or is_mhca) and (not (is_mha and is_mhca)) + + if is_mha: + tensors[k].outputs[0].inputs[0] = tensors[q] + tensors[v].outputs[0].inputs[0] = tensors[q] + del tensors[k] + del tensors[v] + removed += 2 + else: # is_mhca + tensors[k].outputs[0].inputs[0] = tensors[v] + del tensors[k] + removed += 1 + print(f"Removed {removed} QDQ nodes") + return removed + + def get_path(version, pipeline, controlnets=None): if controlnets is not None: return ["lllyasviel/sd-controlnet-" + modality for modality in controlnets] @@ -248,6 +302,7 @@ def __init__(self, verbose=True, framework_model_dir='pytorch_model', fp16=False, + int8=False, max_batch_size=16, text_maxlen=77, embedding_dim=768, @@ -264,6 +319,7 @@ def __init__(self, self.framework_model_dir = framework_model_dir self.fp16 = fp16 + self.int8 = int8 self.min_batch = 1 self.max_batch = max_batch_size @@ -278,6 +334,15 @@ def __init__(self, self.lora_dict = None + def get_pipeline(self): + model_opts = {'variant': 'fp16', 'torch_dtype': torch.float16} if self.fp16 else {} + return DiffusionPipeline.from_pretrained( + self.path, + use_safetensors=self.hf_safetensor, + use_auth_token=self.hf_token, + **model_opts, + ).to(self.device) + def get_model(self, torch_inference=''): pass @@ -300,16 +365,24 @@ def get_shape_dict(self, batch_size, image_height, image_width): return None # Helper utility for ONNX export - def export_onnx(self, onnx_path, onnx_opt_path, onnx_opset, opt_image_height, opt_image_width, enable_lora_merge=False): + def export_onnx( + self, + onnx_path, + onnx_opt_path, + onnx_opset, + opt_image_height, + opt_image_width, + custom_model=None, + enable_lora_merge=False + ): onnx_opt_graph = None # Export optimized ONNX model (if missing) if not os.path.exists(onnx_opt_path): if not os.path.exists(onnx_path): - print(f"Exporting ONNX model: {onnx_path}") - model = self.get_model() - if enable_lora_merge: - model = merge_loras(model, self.lora_dict, self.lora_alphas, self.lora_scales) - with torch.inference_mode(), torch.autocast("cuda"): + print(f"[I] Exporting ONNX model: {onnx_path}") + def export_onnx(model): + if enable_lora_merge: + model = merge_loras(model, self.lora_dict, self.lora_alphas, self.lora_scales) inputs = self.get_sample_input(1, opt_image_height, opt_image_width) torch.onnx.export(model, inputs, @@ -321,10 +394,16 @@ def export_onnx(self, onnx_path, onnx_opt_path, onnx_opset, opt_image_height, op output_names=self.get_output_names(), dynamic_axes=self.get_dynamic_axes(), ) + if custom_model: + with torch.inference_mode(): + export_onnx(custom_model) + else: + with torch.inference_mode(), torch.autocast("cuda"): + export_onnx(self.get_model()) else: print(f"[I] Found cached ONNX model: {onnx_path}") - print(f"Optimizing ONNX model: {onnx_opt_path}") + print(f"[I] Optimizing ONNX model: {onnx_opt_path}") onnx_opt_graph = self.optimize(onnx.load(onnx_path)) if onnx_opt_graph.ByteSize() > 2147483648: onnx.save_model( @@ -385,7 +464,7 @@ def export_weights_map(self, onnx_opt_path, weights_map_path): else: print(f"[I] Found cached weights map: {weights_map_path} ") - def optimize(self, onnx_graph): + def optimize(self, onnx_graph, return_onnx=True, **kwargs): opt = Optimizer(onnx_graph, verbose=self.verbose) opt.info(self.name + ': original') opt.cleanup() @@ -394,7 +473,10 @@ def optimize(self, onnx_graph): opt.info(self.name + ': fold constants') opt.infer_shapes() opt.info(self.name + ': shape inference') - onnx_opt_graph = opt.cleanup(return_onnx=True) + if kwargs.get('fuse_mha_qkv_int8', False): + opt.fuse_mha_qkv_int8_sq() + opt.info(self.name + ': fuse QKV nodes') + onnx_opt_graph = opt.cleanup(return_onnx=return_onnx) opt.info(self.name + ': finished') return onnx_opt_graph @@ -607,6 +689,7 @@ def __init__(self, verbose, framework_model_dir, fp16 = False, + int8 = False, max_batch_size = 16, text_maxlen = 77, controlnets = None, @@ -746,6 +829,7 @@ def __init__(self, verbose, framework_model_dir, fp16 = False, + int8 = False, max_batch_size = 16, text_maxlen = 77, lora_scales = None, @@ -834,6 +918,8 @@ def get_sample_input(self, batch_size, image_height, image_width): } ) + def optimize(self, onnx_graph): + return super().optimize(onnx_graph, fuse_mha_qkv_int8=True) class VAEModel(BaseModel): def __init__(self, diff --git a/demo/Diffusion/requirements.txt b/demo/Diffusion/requirements.txt index e83b1cf2..8e6a8bff 100755 --- a/demo/Diffusion/requirements.txt +++ b/demo/Diffusion/requirements.txt @@ -7,10 +7,11 @@ ftfy matplotlib nvtx onnx==1.14.0 -onnxruntime==1.15.1 +onnxruntime==1.16.1 opencv-python==4.8.0.74 scipy transformers==4.31.0 ---extra-index-url https://pypi.ngc.nvidia.com -onnx-graphsurgeon>=0.3.27 +--extra-index-url https://pypi.nvidia.com +nvidia-ammo==0.7.0 +onnx-graphsurgeon polygraphy diff --git a/demo/Diffusion/stable_diffusion_pipeline.py b/demo/Diffusion/stable_diffusion_pipeline.py index 54a629e1..f4f54257 100644 --- a/demo/Diffusion/stable_diffusion_pipeline.py +++ b/demo/Diffusion/stable_diffusion_pipeline.py @@ -15,6 +15,8 @@ # limitations under the License. # +import ammo.torch.quantization as atq +import calibration from cuda import cudart from diffusers import ( DDIMScheduler, @@ -53,9 +55,14 @@ PIPELINE_TYPE, TRT_LOGGER, Engine, + filter_func, + get_smoothquant_config, get_refit_weights, + load_calib_prompts, merge_loras, prepare_mask_and_masked_image, + quantize_lvl, + replace_lora_layers, save_image, unload_model ) @@ -289,6 +296,11 @@ def getRefitNodesPath(self, model_name, onnx_dir, suffix=''): os.makedirs(onnx_model_dir, exist_ok=True) return os.path.join(onnx_model_dir, 'refit'+suffix+'.json') + def getStateDictPath(self, model_name, onnx_dir, suffix=''): + onnx_model_dir = os.path.join(onnx_dir, self.cachedModelName(model_name)+suffix) + os.makedirs(onnx_model_dir, exist_ok=True) + return os.path.join(onnx_model_dir, 'state_dict.pt') + def loadEngines( self, engine_dir, @@ -303,6 +315,12 @@ def loadEngines( enable_refit=False, enable_all_tactics=False, timing_cache=None, + int8=False, + quantization_level=2.5, + quantization_percentile=0.4, + quantization_alpha=0.6, + calibration_steps=384, + denoising_steps=50, ): """ Build and load engines for TensorRT accelerated inference. @@ -389,20 +407,90 @@ def loadEngines( # Torch fallback for VAE if specified torch_fallback = dict(zip(model_names, [self.torch_inference or (model_name == 'vae' and self.config.get('vae_torch_fallback', False)) for model_name in model_names])) model_suffix = dict(zip(model_names, [lora_suffix if do_lora_merge[model_name] else '' for model_name in model_names])) + use_int8 = dict.fromkeys(model_names, False) + if int8: + assert self.pipeline_type.is_sd_xl(), "int8 quantization only supported for SDXL pipeline" + use_int8['unetxl'] = True + model_suffix['unetxl'] += f"-int8.l{quantization_level}.bs2.s{denoising_steps}.c{calibration_steps}.p{quantization_percentile}.a{quantization_alpha}" onnx_path = dict(zip(model_names, [self.getOnnxPath(model_name, onnx_dir, opt=False, suffix=model_suffix[model_name]) for model_name in model_names])) onnx_opt_path = dict(zip(model_names, [self.getOnnxPath(model_name, onnx_dir, suffix=model_suffix[model_name]) for model_name in model_names])) engine_path = dict(zip(model_names, [self.getEnginePath(model_name, engine_dir, do_engine_refit[model_name], suffix=model_suffix[model_name]) for model_name in model_names])) weights_map_path = dict(zip(model_names, [(self.getWeightsMapPath(model_name, onnx_dir) if do_engine_refit[model_name] else None) for model_name in model_names])) - # Export models to ONNX and save weights name mapping for model_name, obj in self.models.items(): if torch_fallback[model_name]: continue + # Export models to ONNX and save weights name mapping do_export_onnx = not os.path.exists(engine_path[model_name]) and not os.path.exists(onnx_opt_path[model_name]) do_export_weights_map = weights_map_path[model_name] and not os.path.exists(weights_map_path[model_name]) - # FIXME do_export_weights_map needs ONNX graph if do_export_onnx or do_export_weights_map: - obj.export_onnx(onnx_path[model_name], onnx_opt_path[model_name], onnx_opset, opt_image_height, opt_image_width, enable_lora_merge=do_lora_merge[model_name]) + # Non-quantized ONNX export + if not use_int8[model_name]: + obj.export_onnx(onnx_path[model_name], onnx_opt_path[model_name], onnx_opset, opt_image_height, opt_image_width, enable_lora_merge=do_lora_merge[model_name]) + else: + state_dict_path = self.getStateDictPath(model_name, onnx_dir, suffix=model_suffix[model_name]) + if not os.path.exists(state_dict_path): + print(f"[I] Calibrated weights not found, generating {state_dict_path}") + pipeline = obj.get_pipeline() + model = pipeline.unet + replace_lora_layers(model) + calibration_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'calibration-prompts.txt') + # Use batch_size = 2 for UNet calibration + calibration_prompts = load_calib_prompts(2, calibration_file) + # TODO check size > calibration_steps + quant_config = get_smoothquant_config(model, quantization_level) + if quantization_percentile is not None: + quant_config["percentile"] = quantization_percentile + quant_config["base-step"] = int(denoising_steps) + + atq.replace_quant_module(model) + atq.set_quantizer_by_cfg(model, quant_config["quant_cfg"]) + if quantization_percentile is not None: + calibration.precentile_calib_mode(base_unet=model, quant_config=quant_config) + if quantization_alpha is not None: + calibration.reg_alpha_qkv(base_unet=model, alpha=quantization_alpha) + + def do_calibrate(base, calibration_prompts, **kwargs): + for i_th, prompts in enumerate(calibration_prompts): + if i_th >= kwargs["calib_size"]: + return + base( + prompt=prompts, + num_inference_steps=kwargs["n_steps"], + negative_prompt=[ + "normal quality, low quality, worst quality, low res, blurry, nsfw, nude" + ] + * len(prompts), + ).images + + def calibration_loop(): + do_calibrate( + base=pipeline, + calibration_prompts=calibration_prompts, + calib_size=calibration_steps, + n_steps=denoising_steps, + ) + + print(f"[I] Performing int8 calibration for {calibration_steps} steps. This can take a long time.") + calibration.calibrate(model, quant_config["algorithm"], forward_loop=calibration_loop) + torch.save(model.state_dict(), state_dict_path) + + print(f"[I] Generaing quantized ONNX model: {onnx_opt_path[model_name]}") + if not os.path.exists(onnx_path[model_name]): + model = obj.get_model() + replace_lora_layers(model) + atq.replace_quant_module(model) + quant_config = atq.INT8_DEFAULT_CFG + atq.set_quantizer_by_cfg(model, quant_config["quant_cfg"]) + model.load_state_dict(torch.load(state_dict_path), strict=True) + quantize_lvl(model, quantization_level) + atq.disable_quantizer(model, filter_func) + model.to(torch.float32) # QDQ needs to be in FP32 + else: + model = None + obj.export_onnx(onnx_path[model_name], onnx_opt_path[model_name], onnx_opset, opt_image_height, opt_image_width, custom_model=model) + + # FIXME do_export_weights_map needs ONNX graph if do_export_weights_map: print(f"[I] Saving weights map: {weights_map_path[model_name]}") obj.export_weights_map(onnx_opt_path[model_name], weights_map_path[model_name]) @@ -414,10 +502,13 @@ def loadEngines( engine = Engine(engine_path[model_name]) if not os.path.exists(engine_path[model_name]): update_output_names = obj.get_output_names() + obj.extra_output_names if obj.extra_output_names else None - use_tf32 = model_name == 'vae' and self.pipeline_type.is_sd_xl() + extra_build_args = {} + if use_int8[model_name]: + extra_build_args['int8'] = True + extra_build_args['precision_constraints'] = 'prefer' + extra_build_args['builder_optimization_level'] = 4 engine.build(onnx_opt_path[model_name], - fp16=not use_tf32, - tf32=use_tf32, + fp16=True, input_profile=obj.get_input_profile( opt_batch_size, opt_image_height, opt_image_width, static_batch=static_batch, static_shape=static_shape @@ -425,7 +516,8 @@ def loadEngines( enable_refit=do_engine_refit[model_name], enable_all_tactics=enable_all_tactics, timing_cache=timing_cache, - update_output_names=update_output_names) + update_output_names=update_output_names, + **extra_build_args) self.engine[model_name] = engine # Load TensorRT engines @@ -716,9 +808,9 @@ def print_summary(self, denoising_steps, walltime_ms, batch_size): print('|-----------------|--------------|') print('Throughput: {:.2f} image/s'.format(batch_size*1000./walltime_ms)) - def save_image(self, images, pipeline, prompt): + def save_image(self, images, pipeline, prompt, seed): # Save image - image_name_prefix = pipeline+'-fp16'+''.join(set(['-'+prompt[i].replace(' ','_')[:10] for i in range(len(prompt))]))+'-' + image_name_prefix = pipeline+''.join(set(['-'+prompt[i].replace(' ','_')[:10] for i in range(len(prompt))]))+'-'+str(seed)+'-' save_image(images, self.output_dir, image_name_prefix) def infer( @@ -777,7 +869,7 @@ def infer( num_inference_steps = self.denoising_steps - with torch.inference_mode(), torch.autocast("cuda"), trt.Runtime(TRT_LOGGER): + with torch.inference_mode(), trt.Runtime(TRT_LOGGER): torch.cuda.synchronize() e2e_tic = time.perf_counter() @@ -875,7 +967,6 @@ def _get_add_time_ids(original_size, crops_coords_top_left, target_size, dtype, denoiser = 'unetxl' if self.pipeline_type.is_sd_xl() else 'unet' latents = self.denoise_latent(latents, text_embeddings, denoiser=denoiser, **denoise_kwargs) - with torch.inference_mode(), trt.Runtime(TRT_LOGGER): # VAE decode latent (if applicable) if self.return_latents: latents = latents * self.vae_scaling_factor @@ -889,7 +980,7 @@ def _get_add_time_ids(original_size, crops_coords_top_left, target_size, dtype, if not warmup: self.print_summary(num_inference_steps, walltime_ms, batch_size) if not self.return_latents and save_image: - self.save_image(images, self.pipeline_type.name.lower(), prompt) + self.save_image(images, self.pipeline_type.name.lower(), prompt, self.seed) return (latents, walltime_ms) if self.return_latents else (images, walltime_ms) diff --git a/demo/Diffusion/utilities.py b/demo/Diffusion/utilities.py index 1d47342b..44254617 100644 --- a/demo/Diffusion/utilities.py +++ b/demo/Diffusion/utilities.py @@ -17,6 +17,7 @@ from collections import OrderedDict from cuda import cudart +from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear from diffusers.utils.torch_utils import randn_tensor from enum import Enum, auto import gc @@ -38,10 +39,12 @@ save_engine ) import random +import re import requests from scipy import integrate import tensorrt as trt import torch +import types TRT_LOGGER = trt.Logger(trt.Logger.ERROR) @@ -72,6 +75,65 @@ def unload_model(model): torch.cuda.empty_cache() gc.collect() +def replace_lora_layers(model): + def lora_forward(self, x, scale=None): + return self._torch_forward(x) + + for name, module in model.named_modules(): + if isinstance(module, LoRACompatibleConv): + in_channels = module.in_channels + out_channels = module.out_channels + kernel_size = module.kernel_size + stride = module.stride + padding = module.padding + dilation = module.dilation + groups = module.groups + bias = module.bias + + new_conv = torch.nn.Conv2d( + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias=bias is not None, + ) + + new_conv.weight.data = module.weight.data.clone().to(module.weight.data.device) + if bias is not None: + new_conv.bias.data = module.bias.data.clone().to(module.bias.data.device) + + # Replace the LoRACompatibleConv layer with the Conv2d layer + path = name.split(".") + sub_module = model + for p in path[:-1]: + sub_module = getattr(sub_module, p) + setattr(sub_module, path[-1], new_conv) + new_conv._torch_forward = new_conv.forward + new_conv.forward = types.MethodType(lora_forward, new_conv) + + elif isinstance(module, LoRACompatibleLinear): + in_features = module.in_features + out_features = module.out_features + bias = module.bias + + new_linear = torch.nn.Linear(in_features, out_features, bias=bias is not None) + + new_linear.weight.data = module.weight.data.clone().to(module.weight.data.device) + if bias is not None: + new_linear.bias.data = module.bias.data.clone().to(module.bias.data.device) + + # Replace the LoRACompatibleLinear layer with the Linear layer + path = name.split(".") + sub_module = model + for p in path[:-1]: + sub_module = getattr(sub_module, p) + setattr(sub_module, path[-1], new_linear) + new_linear._torch_forward = new_linear.forward + new_linear.forward = types.MethodType(lora_forward, new_linear) + def merge_loras(model, lora_dict, lora_alphas, lora_scales): assert len(lora_scales) == len(lora_dict) for path, lora in lora_dict.items(): @@ -170,11 +232,13 @@ def build(self, onnx_path, fp16=True, tf32=False, + int8=False, input_profile=None, enable_refit=False, enable_all_tactics=False, timing_cache=None, - update_output_names=None + update_output_names=None, + **extra_build_args ): print(f"Building TensorRT engine for {onnx_path}: {self.engine_path}") p = Profile() @@ -183,9 +247,8 @@ def build(self, assert len(dims) == 3 p.add(name, min=dims[0], opt=dims[1], max=dims[2]) - config_kwargs = {} if not enable_all_tactics: - config_kwargs['tactic_sources'] = [] + extra_build_args['tactic_sources'] = [] network = network_from_onnx_path(onnx_path, flags=[trt.OnnxParserFlag.NATIVE_INSTANCENORM]) if update_output_names: @@ -195,10 +258,11 @@ def build(self, network, config=CreateConfig(fp16=fp16, tf32=tf32, + int8=int8, refittable=enable_refit, profiles=[p], load_timing_cache=timing_cache, - **config_kwargs + **extra_build_args ), save_timing_cache=timing_cache ) @@ -333,6 +397,83 @@ def get_refit_weights(state_dict, onnx_opt_path, weight_name_mapping, weight_sha refit_weights[initializer_name] = wt.contiguous() return refit_weights +def load_calib_prompts(batch_size, calib_data_path): + with open(calib_data_path, "r") as file: + lst = [line.rstrip("\n") for line in file] + return [lst[i : i + batch_size] for i in range(0, len(lst), batch_size)] + +def filter_func(name): + pattern = re.compile( + r".*(time_emb_proj|time_embedding|conv_in|conv_out|conv_shortcut|add_embedding).*" + ) + return pattern.match(name) is not None + +def quantize_lvl(unet, quant_level=2.5): + """ + We should disable the unwanted quantizer when exporting the onnx + Because in the current ammo setting, it will load the quantizer amax for all the layers even + if we didn't add that unwanted layer into the config during the calibration + """ + for name, module in unet.named_modules(): + if isinstance(module, torch.nn.Conv2d): + module.input_quantizer.enable() + module.weight_quantizer.enable() + elif isinstance(module, torch.nn.Linear): + if ( + (quant_level >= 2 and "ff.net" in name) + or (quant_level >= 2.5 and ("to_q" in name or "to_k" in name or "to_v" in name)) + or quant_level == 3 + ): + module.input_quantizer.enable() + module.weight_quantizer.enable() + else: + module.input_quantizer.disable() + module.weight_quantizer.disable() + +def get_smoothquant_config(model, quant_level=3): + quant_config = { + "quant_cfg": {}, + "algorithm": "smoothquant", + } + for name, module in model.named_modules(): + w_name = f"{name}*weight_quantizer" + i_name = f"{name}*input_quantizer" + + if ( + w_name in quant_config["quant_cfg"].keys() # type: ignore + or i_name in quant_config["quant_cfg"].keys() # type: ignore + ): + continue + if filter_func(name): + continue + if isinstance(module, torch.nn.Linear): + if ( + (quant_level >= 2 and "ff.net" in name) + or (quant_level >= 2.5 and ("to_q" in name or "to_k" in name or "to_v" in name)) + or quant_level == 3 + ): + quant_config["quant_cfg"][w_name] = {"num_bits": 8, "axis": 0} # type: ignore + quant_config["quant_cfg"][i_name] = {"num_bits": 8, "axis": -1} # type: ignore + elif isinstance(module, torch.nn.Conv2d): + quant_config["quant_cfg"][w_name] = {"num_bits": 8, "axis": 0} # type: ignore + quant_config["quant_cfg"][i_name] = {"num_bits": 8, "axis": None} # type: ignore + return quant_config + +class PercentileAmaxes: + def __init__(self, total_step, percentile) -> None: + self.data = {} + self.total_step = total_step + self.percentile = percentile + self.i = 0 + + def append(self, item): + _cur_step = self.i % self.total_step + if _cur_step not in self.data.keys(): + self.data[_cur_step] = item + else: + self.data[_cur_step] = np.maximum(self.data[_cur_step], item) + self.i += 1 + def add_arguments(parser): # Stable Diffusion configuration parser.add_argument('--version', type=str, default="1.5", choices=["1.4", "1.5", "dreamshaper-7", "2.0-base", "2.0", "2.1-base", "2.1", "xl-1.0", "xl-turbo"], help="Version of Stable Diffusion") @@ -357,6 +498,8 @@ def add_arguments(parser): # TensorRT engine build parser.add_argument('--engine-dir', default='engine', help="Output directory for TensorRT engines") + parser.add_argument('--int8', action='store_true', help="Apply int8 quantization.") + parser.add_argument('--quantization-level', type=float, default=3.0, choices=range(1,4), help="int8/fp8 quantization level, 1: CNN, 2: CNN+FFN, 2.5: CNN+FFN+QKV, 3: CNN+FC") parser.add_argument('--build-static-batch', action='store_true', help="Build TensorRT engines with fixed batch size.") parser.add_argument('--build-dynamic-shape', action='store_true', help="Build TensorRT engines with dynamic image shapes.") parser.add_argument('--build-enable-refit', action='store_true', help="Enable Refit option in TensorRT engines during build.") @@ -386,6 +529,9 @@ def process_pipeline_args(args): if args.use_cuda_graph and (not args.build_static_batch or args.build_dynamic_shape): raise ValueError(f"Using CUDA graph requires static dimensions. Enable `--build-static-batch` and do not specify `--build-dynamic-shape`") + if args.int8 and not args.version.startswith('xl'): + raise ValueError(f"int8 quantization only supported for SDXL pipeline.") + kwargs_init_pipeline = { 'version': args.version, 'max_batch_size': max_batch_size, @@ -413,6 +559,9 @@ def process_pipeline_args(args): 'enable_all_tactics': args.build_all_tactics, 'enable_refit': args.build_enable_refit, 'timing_cache': args.timing_cache, + 'int8': args.int8, + 'quantization_level': args.quantization_level, + 'denoising_steps': args.denoising_steps, } args_run_demo = (args.prompt, args.negative_prompt, args.height, args.width, args.batch_size, args.batch_count, args.num_warmup_runs, args.use_cuda_graph) diff --git a/demo/experimental/HuggingFace-Diffusers/README.md b/demo/experimental/HuggingFace-Diffusers/README.md index 23fef9a0..d0e4e563 100644 --- a/demo/experimental/HuggingFace-Diffusers/README.md +++ b/demo/experimental/HuggingFace-Diffusers/README.md @@ -7,7 +7,7 @@ This demo notebook showcases the acceleration of Stable Diffusion pipeline using ### Clone the TensorRT OSS repository ```bash -git clone git@github.com:NVIDIA/TensorRT.git -b release/9.2 --single-branch +git clone git@github.com:NVIDIA/TensorRT.git -b release/9.3 --single-branch cd TensorRT/demo/experimental/HuggingFace-Diffusers ``` diff --git a/parsers/onnx b/parsers/onnx index 306247b3..1def182e 160000 --- a/parsers/onnx +++ b/parsers/onnx @@ -1 +1 @@ -Subproject commit 306247b30da17ec10b578d17c408fe04864d6fe5 +Subproject commit 1def182e2a04e939130b1070151a048321c6719c diff --git a/tools/Polygraphy/CHANGELOG.md b/tools/Polygraphy/CHANGELOG.md index 29478e1b..85e30fba 100644 --- a/tools/Polygraphy/CHANGELOG.md +++ b/tools/Polygraphy/CHANGELOG.md @@ -2,6 +2,67 @@ Dates are in YYYY-MM-DD format. + +## v0.49.7 (2024-02-02) +### Added +- Added `plugin match` subtool that finds opportunities for plugin substitution in an ONNX Model and prepares an intermediate file to be used for actual substitution +- Added `plugin list` subtool that lists opportunities for plugin substitution, without preparing an intermediate file. +- Added support for building engines with the refittable weights stripped. + Setting the `strip_plan` parameter of `CreateConfig` or passing in the `--strip-plan` flag + enables building engines with the refittable weights stripped. +- Added `plugin replace` subtool that replaces subgraphs with plugins, based on an intermediate file (config.yaml) +- Added `polygraphy surgeon weight-strip` to strip the initializers of selective nodes in an ONNX model +- Added `polygraphy surgeon weight-reconstruct` to read a weightless ONNX model and fill the empty initializers with proxy tensors +- Added '--weight-streaming` and `--weight-streaming-budget` APIs to control TRT weight streaming + + +## v0.49.6 (2024-01-18) +### Fixed +- Fixed a bug where `explicit_batch` would be provided by default on TRT 10.0, where it has been removed. + + +## v0.49.5 (2024-01-16) +### Added +- Added an `allocation_strategy` to `TrtRunner` and corresponding `--allocation-strategy` argument to CLI tools. + +### Fixed +- Fixed a bug where the reference count of the TensorRT engine would not be decremented correctly in `TrtRunner`. + + +## v0.49.4 (2023-12-20) +### Fixed +- Fixed a bug where the comparator would modify the original output tensor in some cases instead of operating on a copy. +- Fixed a bug where `is_installed()` for lazily imported modules would not work if the package name differed from the module name. + + +## v0.49.3 (2023-11-30) +### Changed +- Improved error messages in the default data loader for invalid backend modules. + + +## v0.49.2 (2023-11-27) +### Added +- Added `DataType.INT4` for 4-bit signed integers. + +### Changed +- Removed internal usage of several deprecated TensorRT APIs. + +### Fixed +- Fixed a bug in the default data loader where scalars would not be generated correctly. + + +## v0.49.1 (2023-10-03) +### Added +- Added `--profiling-verbosity` command-line option. +- Added a `progress_monitor` parameter to `CreateConfig`. +- Added a `data_loader_backend_module` parameter to `DataLoader` and corresponding `--data-loader-backend-module` argument to CLI tools to + choose between generating `numpy.ndarray` and `torch.tensor` in the default dataloader. + +### Fixed +- Fixed a bug where warnings would be issued for unsupported versions of `torch` even if + `torch` was not being used. + + ## v0.49.0 (2023-07-28) ### Added - Added `check lint` subtool that validates ONNX Models and generates human-readable console output and diff --git a/tools/Polygraphy/docs/index.rst b/tools/Polygraphy/docs/index.rst index 497a9453..f02e6b1c 100644 --- a/tools/Polygraphy/docs/index.rst +++ b/tools/Polygraphy/docs/index.rst @@ -11,6 +11,11 @@ see `the GitHub repository `_. +.. warning:: + Any APIs not documented here should be considered internal only and do not adhere to the + deprecation policy for public APIs. Thus, they may be modified or removed at any time without warning. + Avoid using undocumented APIs! + .. toctree:: :hidden: @@ -36,7 +41,6 @@ see `this page NestedLocalFake2 | Fake_2 | nested_node_fake_2 | In node 0 with name: nested_node_fake_2 and operator: Fake_2 (checkFallbackPluginImporter): INVALID_NODE: creator && "Plugin not found, are the plugin name, version, and namespace correct?" + OuterFunction | Fake_1 | nested_node_fake_1 | In node 0 with name: nested_node_fake_1 and operator: Fake_1 (checkFallbackPluginImporter): INVALID_NODE: creator && "Plugin not found, are the plugin name, version, and namespace correct?" + ``` + +## Understanding The Output + +In this example, `nested_local_function.onnx` contains `Fake_1` and `Fake_2` nodes that are not supported by TensorRT. `Fake_1` node is located inside a local function `OuterFunction` and `Fake_2` node is located inside a nested local function, `NestedLocalFake2`. +The summary table shows the current stack trace consisting of local functions, the operator in which the error occurred and the reason it's unsupported. + +For more information and options, see `polygraphy inspect capability --help`. diff --git a/tools/Polygraphy/examples/cli/inspect/09_inspecting_tensorrt_static_onnx_support/nested_local_function.onnx b/tools/Polygraphy/examples/cli/inspect/09_inspecting_tensorrt_static_onnx_support/nested_local_function.onnx new file mode 100644 index 0000000000000000000000000000000000000000..a769cc83501635c683aafd9ce0b1bf6f9a62587b GIT binary patch literal 807 zcmaKq%TB^T6oxqkNj=_h)KKfLOA-_5&MsVFWI+f=MABA5d86l(bm}4Q>qvCebS-_a7H;7njU)$p z4Xm~HdS|E(Yrz`+09zv##cVbdQ+L^SKbUP9Jd12>=ka8@&UwjCzPIy$m1U&5%p^s} zOrd!)p4wJ3#h~^eLw{7k*YH!vO)agqYDkW3s2>G!537m@jLl}@S@X{0k@P#KsIyP$ zrp8%2-uvZ^w}QGu5ys@AQkAXOpa@@b5Y8=b>rP-&fZEp4Fc^u@cbDFGM%mF30z+GY Z=3FC8NTm=Z9&`&2y155~^7ARN<{!!O7ODUM literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/README.md b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/README.md new file mode 100644 index 00000000..d0679707 --- /dev/null +++ b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/README.md @@ -0,0 +1,122 @@ +# Matching and replacing a subgraph with a plugin in an onnx model + + +## Introduction + +The `plugin` tool offers subtools to find and replace subgraphs in an onnx model. + +Subgraph substition is a three-step process: +1. Find matching subgraphs based on the plugin's graph pattern (pattern.py) and list the potential substitutions in a user-editable intermediate file (config.yaml) +2. Review and edit (if necessary) the list of potential substitutions (config.yaml) +3. Replace subgraphs with plugins based on the list of potential substitutions (config.yaml) + + +`original.onnx` -------> `match` -------> `config.yaml` -------> `replace` -------> `replaced.onnx` +`plugins` ----------------^ `usr input`---^ `plugins`--------^ + +## Details + +### Match +Finding matchings subgraphs in a model is done based on a graph pattern description (`pattern.py`) provided by the plugins. +The graph pattern description (`pattern.py`) contains information about the topology and additional constraints for the graph nodes, and a way to calculate the plugin's attributes based on the matching subgraph. +Only plugins which provide a graph pattern description (pattern.py) are considered for matching. + +The result of the matching is stored in an intermediate file called `config.yaml`. +The user should review and edit this file, as it serves as a TODO list for the replacement step. For example, if there are 2 matching subgraphs, but only one should be substituted, the result can be removed from the file. + +As a preview/dry-run step, the `plugin list` subtool can show the list of potential substitutions without generating an intermediate file. + +### Replace +Replacement of subgraphs with plugins uses the `config.yaml` file generated in the matching stage. Any matching subgraph listed in this file is going to be removed and replaced with a single node representing the plugin. The original file is kept, and a new file is saved where the replacements are done. This file by default is called `replaced.onnx`. + +### Compare +The original and the replaced model can be compared to check if they behave the same way before and after plugin substitution: +`polygraphy run original.onnx --trt --save-outputs model_output.json` +`polygraphy run replaced.onnx --trt --load-outputs model_output.json` + +## Running The Example + +1. Find and save matches of toyPlugin in the example network: + + ```bash + polygraphy plugin match graph_with_subgraph_matching_toy_plugin.onnx \ + --plugin-dir ./plugins + ``` + + + This will display something like: + + ``` + checking toyPlugin in model + [I] Start a subgraph matching... + [I] Checking node: n1 against pattern node: Anode. + [I] No match because: Op did not match. Node op was: O but pattern op was: A. + [I] Start a subgraph matching... + [I] Found a matched subgraph! + [I] Start a subgraph matching... + ``` + + The resulting config.yaml will look like: + + ``` + name: toyPlugin + instances: + - inputs: + - i1 + - i1 + outputs: + - o1 + - o2 + attributes: + x: 1 + ``` + + +2. **[Optional]** List matches of toyPlugin in the example network, without saving config.yaml: + + ```bash + polygraphy plugin list graph_with_subgraph_matching_toy_plugin.onnx \ + --plugin-dir ./plugins + ``` + + + This will display something like: + + ``` + checking toyPlugin in model + [I] Start a subgraph matching... + [I] Checking node: n1 against pattern node: Anode. + [I] No match because: Op did not match. Node op was: O but pattern op was: A. + [I] Start a subgraph matching... + ... + [I] Found a matched subgraph! + [I] Start a subgraph matching... + [I] Checking node: n6 against pattern node: Anode. + [I] No match because: Op did not match. Node op was: E but pattern op was: A. + the following plugins would be used: + {'toyPlugin': 1} + ``` + + There will be no resulting config.yaml, as this command is only for printing the number of matches per plugin + + +The `plugin replace` subtool replaces subgraphs in an onnx model with plugins + + +3. Replace parts of the example network with toyPlugin: + + ```bash + polygraphy plugin replace graph_with_subgraph_matching_toy_plugin.onnx \ + --plugin-dir ./plugins \ + -o replaced.onnx + ``` + + + This will display something like: + + ``` + [I] Loading model: /Users/pkisfaludi/Documents/git/Polygraphy/examples/cli/plugin/03_replace_subgraph_with_a_plugin/graph_with_subgraph_matching_toy_plugin.onnx + ``` + + The result file is replaced.onnx, where a subgraph in the example network is replaced by toyPlugin + \ No newline at end of file diff --git a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/graph_with_subgraph_matching_toy_plugin.onnx b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/graph_with_subgraph_matching_toy_plugin.onnx new file mode 100644 index 0000000000000000000000000000000000000000..9dde8cb7b49312e36402cbcb945182c5c50049eb GIT binary patch literal 240 zcmdJ957XX=Pro`wfB%Ys_R}r6H zlvt2aTw0W#nx6;ZL~(%~!@|YH!I%VO=Nk$k*hUa`7Dx?L9Lz=*H%3xp0%1F`a4`sQ F0|3!5CK><$ literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/__init__.py b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/pattern.py b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/pattern.py new file mode 100644 index 00000000..6cf600ba --- /dev/null +++ b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/pattern.py @@ -0,0 +1,31 @@ +from polygraphy import mod +gs = mod.lazy_import("onnx_graphsurgeon>=0.5.0") + +def get_plugin_pattern() -> gs.GraphPattern: + """ + Toy plugin pattern: + A B + \ / + C, attrs['x'] < 2.0 + / \ + D E + """ + pattern = gs.GraphPattern() + in_0 = pattern.variable() + in_1 = pattern.variable() + a_out = pattern.add("Anode", "A", inputs=[in_0]) + b_out = pattern.add("Bnode", "B", inputs=[in_1]) + check_function = lambda node : node.attrs["x"] < 2.0 + c_out = pattern.add("Cnode", "C", inputs=[a_out, b_out], check_func=check_function) + d_out = pattern.add("Dnode", "D", inputs=[c_out]) + e_out = pattern.add("Enode", "E", inputs=[c_out]) + pattern.set_output_tensors([d_out, e_out]) + + return pattern + +def get_plugin_attributes(sg) -> dict: + """ + example plugin attribute mapping, where the plugin has attribute ToyX, which gets its value from C.x * 2 + """ + return {"ToyX": int(sg.get("Cnode").attrs["x"]) * 2} + diff --git a/tools/Polygraphy/examples/cli/run/01_comparing_frameworks/README.md b/tools/Polygraphy/examples/cli/run/01_comparing_frameworks/README.md index e36a3880..0f288ad2 100644 --- a/tools/Polygraphy/examples/cli/run/01_comparing_frameworks/README.md +++ b/tools/Polygraphy/examples/cli/run/01_comparing_frameworks/README.md @@ -48,8 +48,8 @@ polygraphy run dynamic_identity.onnx --trt --onnxrt \ ### Comparing TensorRT Precisions To build a TensorRT engine with reduced precision layers for comparison against -ONNXRT, use one of the supported precision flags (`--tf32`, `--fp16`, or -`--int8`). For example: +ONNXRT, use one of the supported precision flags (e.g. `--tf32`, `--fp16`,`--int8`, etc.). +For example: ```bash polygraphy run dynamic_identity.onnx --trt --fp16 --onnxrt \ @@ -89,7 +89,7 @@ reldiff = absdiff / abs(out1) Then, for each index `i` in the output, Polygraphy checks whether -`absdiff[i] > atol and reldiff[i] > rtol`. If any index satisfies this, +`absdiff[i] > atol and reldiff[i] > rtol`. If any index satisfies this, then the comparison will fail. This is less stringent than comparing the maximum absolute and relative error across the entire tensor (`--check-error-stat max`) since if *different* indices `i` and `j` satisfy `absdiff[i] > atol` and `reldiff[j] > rtol`, diff --git a/tools/Polygraphy/polygraphy/README.md b/tools/Polygraphy/polygraphy/README.md index c137d650..3b3d1dc0 100644 --- a/tools/Polygraphy/polygraphy/README.md +++ b/tools/Polygraphy/polygraphy/README.md @@ -27,8 +27,8 @@ with the `--gen-script` option to auto-generate template scripts that use the Po > :warning: Any APIs not documented in the [API reference documentation](#python-api-reference-documentation) should be considered internal only and do not adhere to the [deprecation policy](#deprecation-policy) - as the public APIs do. Thus, they may be modified or removed at any time without warning. - Avoid using these internal APIs outside of Polygraphy! + for public APIs. Thus, they may be modified or removed at any time without warning. + Avoid using undocumented APIs! ## Backends diff --git a/tools/Polygraphy/polygraphy/__init__.py b/tools/Polygraphy/polygraphy/__init__.py index 4912eceb..01a4c1f1 100644 --- a/tools/Polygraphy/polygraphy/__init__.py +++ b/tools/Polygraphy/polygraphy/__init__.py @@ -1,3 +1,3 @@ import polygraphy.config -__version__ = "0.49.0" +__version__ = "0.49.7" diff --git a/tools/Polygraphy/polygraphy/backend/onnx/loader.py b/tools/Polygraphy/polygraphy/backend/onnx/loader.py index 6c5798bf..6992ad00 100644 --- a/tools/Polygraphy/polygraphy/backend/onnx/loader.py +++ b/tools/Polygraphy/polygraphy/backend/onnx/loader.py @@ -396,8 +396,9 @@ def run_const_fold_pass(model): model = infer_shapes(model, allow_onnxruntime=self.allow_onnxruntime_shape_inference) return model + # Need to manually trigger the autoinstall this since it's used by ONNX-GS, which does not have an autoinstall mechanism. mod.autoinstall(onnxrt) - if not mod.has_mod("onnxruntime"): + if not onnxrt.is_installed() or not onnxrt.is_importable(): G_LOGGER.error( f"ONNX-Runtime is not installed, so constant folding may be suboptimal or not work at all.\n" f"Consider installing ONNX-Runtime: {sys.executable} -m pip install onnxruntime" diff --git a/tools/Polygraphy/polygraphy/backend/onnxrt/runner.py b/tools/Polygraphy/polygraphy/backend/onnxrt/runner.py index b15337dd..2c4bf7a0 100644 --- a/tools/Polygraphy/polygraphy/backend/onnxrt/runner.py +++ b/tools/Polygraphy/polygraphy/backend/onnxrt/runner.py @@ -46,7 +46,11 @@ def activate_impl(self): def get_input_metadata_impl(self): meta = TensorMetadata() for node in self.sess.get_inputs(): - meta.add(node.name, dtype=DataType.from_dtype(node.type, "onnxruntime"), shape=node.shape) + meta.add( + node.name, + dtype=DataType.from_dtype(node.type, "onnxruntime"), + shape=node.shape, + ) return meta @util.check_called_by("infer") diff --git a/tools/Polygraphy/polygraphy/backend/tf/__init__.py b/tools/Polygraphy/polygraphy/backend/tf/__init__.py index e7240f27..d9de2111 100644 --- a/tools/Polygraphy/polygraphy/backend/tf/__init__.py +++ b/tools/Polygraphy/polygraphy/backend/tf/__init__.py @@ -11,7 +11,7 @@ def set_tf_logging_level(severity_trie): tf = mod.lazy_import("tensorflow<2.0") - if not mod.has_mod("tensorflow"): + if not tf.is_installed() or not tf.is_importable(): return sev = severity_trie.get(G_LOGGER.module_path(os.path.dirname(__file__))) diff --git a/tools/Polygraphy/polygraphy/backend/trt/algorithm_selector.py b/tools/Polygraphy/polygraphy/backend/trt/algorithm_selector.py index c4a9db0b..35f4153c 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/algorithm_selector.py +++ b/tools/Polygraphy/polygraphy/backend/trt/algorithm_selector.py @@ -37,7 +37,9 @@ def check_is_instance(obj, cls, name): if not isinstance(obj, cls): - G_LOGGER.critical(f"'{name}' must be an instance of {cls.__name__}, but is: {obj}.") + G_LOGGER.critical( + f"'{name}' must be an instance of {cls.__name__}, but is: {obj}." + ) @mod.export() @@ -58,7 +60,6 @@ def from_trt(io_info): TensorInfo """ return TensorInfo( - io_info.tensor_format, io_info.dtype, tuple(io_info.strides), # These fields were added in 8.6 @@ -66,16 +67,14 @@ def from_trt(io_info): util.try_getattr(io_info, "components_per_element"), ) - def __init__(self, tensor_format, dtype, strides, vectorized_dim, components_per_element): + def __init__(self, dtype, strides, vectorized_dim, components_per_element): """ Args: - tensor_format (trt.TensorFormat): The tensor format. dtype (trt.DataType): The data type. strides (Sequence[int]): The strides. vectorized_dim (int): The index of the vectorized dimensions. components_per_element (int): The number of components per element. """ - check_is_instance(tensor_format, trt.TensorFormat, "tensor_format") check_is_instance(dtype, trt.DataType, "dtype") check_is_instance(strides, Sequence, "strides") if vectorized_dim is not None: @@ -83,7 +82,6 @@ def __init__(self, tensor_format, dtype, strides, vectorized_dim, components_per if components_per_element is not None: check_is_instance(components_per_element, int, "components_per_element") - self.tensor_format = tensor_format self.dtype = dtype self.strides = tuple(strides) self.vectorized_dim = vectorized_dim @@ -93,16 +91,17 @@ def __eq__(self, other): return self.__dict__ == other.__dict__ def __repr__(self): - return f"TensorInfo({str(self.tensor_format)}, {str(self.dtype)}, {self.strides}, {self.vectorized_dim}, {self.components_per_element})" + return f"TensorInfo({str(self.dtype)}, {self.strides}, {self.vectorized_dim}, {self.components_per_element})" def __hash__(self): - return hash((self.tensor_format, self.dtype, self.strides, self.vectorized_dim, self.components_per_element)) + return hash( + (self.dtype, self.strides, self.vectorized_dim, self.components_per_element) + ) @Encoder.register(TensorInfo) def encode(tensor_info): return { - "tensor_format": str(tensor_info.tensor_format), "dtype": str(tensor_info.dtype), "strides": tensor_info.strides, "vectorized_dim": tensor_info.vectorized_dim, @@ -113,7 +112,6 @@ def encode(tensor_info): @Decoder.register(TensorInfo) def decode(dct): return TensorInfo( - util.getattr_nested(trt, dct["tensor_format"]), util.getattr_nested(trt, dct["dtype"]), dct["strides"], dct["vectorized_dim"], @@ -146,7 +144,10 @@ def from_trt(context, algorithm): implementation = algorithm.algorithm_variant.implementation tactic = algorithm.algorithm_variant.tactic - inputs = tuple(TensorInfo.from_trt(algorithm.get_algorithm_io_info(i)) for i in range(context.num_inputs)) + inputs = tuple( + TensorInfo.from_trt(algorithm.get_algorithm_io_info(i)) + for i in range(context.num_inputs) + ) outputs = tuple( TensorInfo.from_trt(algorithm.get_algorithm_io_info(i)) for i in range(context.num_inputs, context.num_inputs + context.num_outputs) @@ -233,7 +234,10 @@ def add(self, name, algorithm): def __str__(self): return "\n".join( - [f"Layer: {name}\n{constants.TAB}Algorithm: {algorithm}" for (name, algorithm) in self.items()] + [ + f"Layer: {name}\n{constants.TAB}Algorithm: {algorithm}" + for (name, algorithm) in self.items() + ] ) diff --git a/tools/Polygraphy/polygraphy/backend/trt/config.py b/tools/Polygraphy/polygraphy/backend/trt/config.py index 3f19064e..41fc14c3 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/config.py +++ b/tools/Polygraphy/polygraphy/backend/trt/config.py @@ -50,6 +50,7 @@ def __init__( profiling_verbosity=None, memory_pool_limits=None, refittable=None, + strip_plan=None, preview_features=None, engine_capability=None, direct_io=None, @@ -63,6 +64,8 @@ def __init__( error_on_timing_cache_miss=None, bf16=None, disable_compilation_cache=None, + progress_monitor=None, + weight_streaming=None, ): """ Creates a TensorRT IBuilderConfig that can be used by EngineFromNetwork. @@ -135,6 +138,9 @@ def __init__( refittable (bool): Enables the engine to be refitted with new weights after it is built. Defaults to False. + strip_plan (bool): + Strips the refittable weights from the engine plan file. + Defaults to False. preview_features (List[trt.PreviewFeature]): The preview features to enable. Use an empty list to disable all preview features. @@ -184,6 +190,10 @@ def __init__( disable_compilation_cache (bool): Whether to disable caching JIT-compiled code. Defaults to False. + progress_monitor (trt.IProgressMonitor): + A progress monitor. Allow users to view engine building progress through CLI. + weight_streaming (bool): + TWhether to enable weight streaming for the TensorRT Engine. """ self.tf32 = util.default(tf32, False) self.fp16 = util.default(fp16, False) @@ -195,6 +205,7 @@ def __init__( self.precision_constraints = precision_constraints self.restricted = util.default(restricted, False) self.refittable = util.default(refittable, False) + self.strip_plan = util.default(strip_plan, False) self.timing_cache_path = load_timing_cache self.algorithm_selector = algorithm_selector self.sparse_weights = util.default(sparse_weights, False) @@ -212,8 +223,12 @@ def __init__( self.version_compatible = version_compatible self.exclude_lean_runtime = exclude_lean_runtime self.quantization_flags = quantization_flags - self.error_on_timing_cache_miss = util.default(error_on_timing_cache_miss, False) + self.error_on_timing_cache_miss = util.default( + error_on_timing_cache_miss, False + ) self.disable_compilation_cache = util.default(disable_compilation_cache, False) + self.progress_monitor = progress_monitor + self.weight_streaming = weight_streaming if self.calibrator is not None and not self.int8: G_LOGGER.warning( @@ -243,12 +258,17 @@ def try_run(func, name): trt_util.fail_unavailable(f"{name} in CreateConfig") def try_set_flag(flag_name): - return try_run(lambda: config.set_flag(getattr(trt.BuilderFlag, flag_name)), flag_name.lower()) + return try_run( + lambda: config.set_flag(getattr(trt.BuilderFlag, flag_name)), + flag_name.lower(), + ) if self.preview_features is not None: for preview_feature in trt.PreviewFeature.__members__.values(): try_run( - lambda: config.set_preview_feature(preview_feature, preview_feature in self.preview_features), + lambda: config.set_preview_feature( + preview_feature, preview_feature in self.preview_features + ), "preview_features", ) @@ -286,6 +306,9 @@ def try_set_flag(flag_name): if self.refittable: try_set_flag("REFIT") + if self.strip_plan: + try_set_flag("STRIP_PLAN") + if self.direct_io: try_set_flag("DIRECT_IO") @@ -306,17 +329,27 @@ def try_set_flag(flag_name): if self.int8: try_set_flag("INT8") - if not network.has_explicit_precision: + # No Q/DQ layers means that we will need to calibrate. + if not any( + layer.type in [trt.LayerType.QUANTIZE, trt.LayerType.DEQUANTIZE] + for layer in network + ): if self.calibrator is not None: config.int8_calibrator = self.calibrator try: - config.set_calibration_profile(calib_profile.to_trt(builder, network)) + config.set_calibration_profile( + calib_profile.to_trt(builder, network) + ) G_LOGGER.info(f"Using calibration profile: {calib_profile}") except AttributeError: - G_LOGGER.extra_verbose("Cannot set calibration profile on TensorRT 7.0 and older.") + G_LOGGER.extra_verbose( + "Cannot set calibration profile on TensorRT 7.0 and older." + ) trt_util.try_setup_polygraphy_calibrator( - config, network, calib_profile=calib_profile.to_trt(builder, network) + config, + network, + calib_profile=calib_profile.to_trt(builder, network), ) else: G_LOGGER.warning( @@ -342,26 +375,34 @@ def set_profiling_verbosity(): try_run(set_profiling_verbosity, name="profiling_verbosity") else: try: - config.profiling_verbosity = trt.ProfilingVerbosity.VERBOSE + config.profiling_verbosity = trt.ProfilingVerbosity.DETAILED except AttributeError: pass if self.memory_pool_limits is not None: for pool_type, pool_size in self.memory_pool_limits.items(): - try_run(lambda: config.set_memory_pool_limit(pool_type, pool_size), name="memory_pool_limits") + try_run( + lambda: config.set_memory_pool_limit(pool_type, pool_size), + name="memory_pool_limits", + ) if self.tactic_sources is not None: tactic_sources_flag = 0 for source in self.tactic_sources: tactic_sources_flag |= 1 << int(source) - try_run(lambda: config.set_tactic_sources(tactic_sources_flag), name="tactic_sources") + try_run( + lambda: config.set_tactic_sources(tactic_sources_flag), + name="tactic_sources", + ) try: cache = None if self.timing_cache_path: try: with util.LockFile(self.timing_cache_path): - timing_cache_data = util.load_file(self.timing_cache_path, description="tactic timing cache") + timing_cache_data = util.load_file( + self.timing_cache_path, description="tactic timing cache" + ) cache = config.create_timing_cache(timing_cache_data) except FileNotFoundError: G_LOGGER.warning( @@ -387,7 +428,9 @@ def set_algo_selector(): try_run(set_algo_selector, name="algorithm_selector") if not self.timing_cache_path: - G_LOGGER.warning("Disabling tactic timing cache because algorithm selector is enabled.") + G_LOGGER.warning( + "Disabling tactic timing cache because algorithm selector is enabled." + ) try_set_flag("DISABLE_TIMING_CACHE") if self.engine_capability is not None: @@ -416,7 +459,9 @@ def set_hardware_compatibility_level(): if self.exclude_lean_runtime: if not self.version_compatible: - G_LOGGER.critical(f"Cannot set EXCLUDE_LEAN_RUNTIME if version compatibility is not enabled. ") + G_LOGGER.critical( + f"Cannot set EXCLUDE_LEAN_RUNTIME if version compatibility is not enabled. " + ) try_set_flag("EXCLUDE_LEAN_RUNTIME") if self.hardware_compatibility_level is not None or self.version_compatible: @@ -447,10 +492,20 @@ def set_max_aux_streams(): if self.error_on_timing_cache_miss: try_set_flag("ERROR_ON_TIMING_CACHE_MISS") - + if self.disable_compilation_cache: try_set_flag("DISABLE_COMPILATION_CACHE") + if self.progress_monitor is not None: + + def set_progress_monitor(): + config.progress_monitor = self.progress_monitor + + try_run(set_progress_monitor, name="progress_monitor") + + if self.weight_streaming: + try_set_flag("WEIGHT_STREAMING") + return config @@ -475,7 +530,9 @@ def __init__(self, config, func): # Sanity-check that the function passed in is callable if not callable(func): - G_LOGGER.critical(f"Object {func} (of type {type(func)}) is not a callable.") + G_LOGGER.critical( + f"Object {func} (of type {type(func)}) is not a callable." + ) self._func = func diff --git a/tools/Polygraphy/polygraphy/backend/trt/loader.py b/tools/Polygraphy/polygraphy/backend/trt/loader.py index c26cc9d3..ae726f0e 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/loader.py +++ b/tools/Polygraphy/polygraphy/backend/trt/loader.py @@ -91,7 +91,7 @@ def __init__(self, explicit_batch=None, strongly_typed=None): Whether to mark the network as being strongly typed. Defaults to False. """ - self.explicit_batch = util.default(explicit_batch, True) + self.explicit_batch = util.default(explicit_batch, True if mod.version(trt.__version__) < mod.version("10.0") else None) self.strongly_typed = util.default(strongly_typed, False) @util.check_called_by("__call__") @@ -104,7 +104,10 @@ def call_impl(self): network_flags = 0 if self.explicit_batch: - network_flags |= 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) + try: + network_flags |= 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) + except AttributeError: + trt_util.fail_unavailable("explicit_batch") if self.strongly_typed: try: @@ -119,31 +122,38 @@ def call_impl(self): class BaseNetworkFromOnnx(BaseLoader): - def __init__(self, flags=None, strongly_typed=None): + def __init__(self, flags=None, plugin_instancenorm=None, strongly_typed=None): """ Args: flags (List[trt.OnnxParserFlag]): A list of ``OnnxParserFlag`` s to modify the default parsing behavior of the ONNX parser. Defaults to None. + plugin_instancenorm (bool): + Whether to force usage of the plugin implementation of ONNX + InstanceNorm by clearing the NATIVE_INSTANCENORM flag in the parser. + Defaults to False strongly_typed (bool): Whether to mark the network as being strongly typed. Defaults to False. """ self.flags = flags + self.plugin_instancenorm=util.default(plugin_instancenorm, False) self.strongly_typed = util.default(strongly_typed, False) @util.check_called_by("__call__") def call_impl(self): builder, network = create_network(strongly_typed=self.strongly_typed) parser = trt.OnnxParser(network, trt_util.get_trt_logger()) - # Set flags if applicable + # Set flags if applicable. if mod.version(trt.__version__) >= mod.version("8.6"): if self.flags: masked_flags = 0 for f in self.flags: masked_flags |= 1 << int(f) parser.flags = masked_flags + if self.plugin_instancenorm: + parser.clear_flag(trt.OnnxParserFlag.NATIVE_INSTANCENORM) return builder, network, parser @@ -153,7 +163,7 @@ class NetworkFromOnnxBytes(BaseNetworkFromOnnx): Functor that parses an ONNX model to create a trt.INetworkDefinition. """ - def __init__(self, model_bytes, flags=None, strongly_typed=None): + def __init__(self, model_bytes, flags=None, plugin_instancenorm=None, strongly_typed=None): """ Parses an ONNX model. @@ -165,11 +175,15 @@ def __init__(self, model_bytes, flags=None, strongly_typed=None): A list of ``OnnxParserFlag`` s to modify the default parsing behavior of the ONNX parser. Defaults to None. + plugin_instancenorm (bool): + Whether to force usage of the plugin implementation of ONNX + InstanceNorm by clearing the NATIVE_INSTANCENORM flag in the parser. + Defaults to False strongly_typed (bool): Whether to mark the network as being strongly typed. Defaults to False. """ - super().__init__(flags=flags, strongly_typed=strongly_typed) + super().__init__(flags=flags, plugin_instancenorm=plugin_instancenorm, strongly_typed=strongly_typed) self._model_bytes = model_bytes @util.check_called_by("__call__") @@ -193,7 +207,7 @@ class NetworkFromOnnxPath(BaseNetworkFromOnnx): This loader supports models with weights stored in an external location. """ - def __init__(self, path, flags=None, strongly_typed=None): + def __init__(self, path, flags=None, plugin_instancenorm=None, strongly_typed=None): """ Parses an ONNX model from a file. @@ -204,11 +218,15 @@ def __init__(self, path, flags=None, strongly_typed=None): A list of ``OnnxParserFlag`` s to modify the default parsing behavior of the ONNX parser. Defaults to None. + plugin_instancenorm (bool): + Whether to force usage of the plugin implementation of ONNX + InstanceNorm by clearing the NATIVE_INSTANCENORM flag in the parser. + Defaults to False strongly_typed (bool): Whether to mark the network as being strongly typed. Defaults to False. """ - super().__init__(flags=flags, strongly_typed=strongly_typed) + super().__init__(flags=flags, plugin_instancenorm=plugin_instancenorm, strongly_typed=strongly_typed) self.path = path @util.check_called_by("__call__") diff --git a/tools/Polygraphy/polygraphy/backend/trt/runner.py b/tools/Polygraphy/polygraphy/backend/trt/runner.py index b9e9099c..471e2992 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/runner.py +++ b/tools/Polygraphy/polygraphy/backend/trt/runner.py @@ -110,7 +110,7 @@ class TrtRunner(BaseRunner): be used only for prototyping, testing, and debugging. """ - def __init__(self, engine, name: str = None, optimization_profile: int = None): + def __init__(self, engine, name: str = None, optimization_profile: int = None, allocation_strategy: str = None, weight_streaming_budget: int = None): """ Args: engine (Union[Union[trt.ICudaEngine, trt.IExecutionContext], Callable() -> Union[trt.ICudaEngine, trt.IExecutionContext]]): @@ -124,10 +124,23 @@ def __init__(self, engine, name: str = None, optimization_profile: int = None): The index of the optimization profile to set each time this runner is activated. When this is not provided, the profile is not set explicitly and will default to the 0th profile. You can also change the profile after the runner is active using the ``set_profile()`` method. + allocation_strategy (str): + The way device memory (internal activation and scratch memory) is allocated for the execution context. The value of this argument can be: + - "static": The default value. The execution context will pre-allocate a block of memory that is sufficient for any possible input size across all profiles. + - "profile": Allocate device memory enough for the current profile based on profile max shapes. + - "runtime": Allocate device meomry enough for the current input shapes. + weight_streaming_budget (str): + The amount of GPU memory in bytes that TensorRT can use for weights at runtime. It can take on the following values + - None: TensorRT will decide the streaming budget automatically. + - -1: Disables weight streaming at runtime + - 0: Sets weight streaming budget to minimum budget. + - >0: The amount of memory in bytes TensorRT can use for weights. """ super().__init__(name=name, prefix="trt-runner") self._engine_or_context = engine self.optimization_profile = optimization_profile + self.allocation_strategy = allocation_strategy + self.weight_streaming_budget = weight_streaming_budget @util.check_called_by("activate") def activate_impl(self): @@ -135,12 +148,33 @@ def activate_impl(self): if isinstance(engine_or_context, trt.ICudaEngine): self.engine = engine_or_context - self.context = self.engine.create_execution_context() + if self.weight_streaming_budget is not None: + if self.weight_streaming_budget == 0: + min_budget = self.engine.minimum_weight_streaming_budget + self.engine.weight_streaming_budget = min_budget + elif self.weight_streaming_budget == -1: + self.engine.weight_streaming_budget = -1 + else: + self.engine.weight_streaming_budget = self.weight_streaming_budget + + allocation_strategy = util.default(self.allocation_strategy, "static") + if allocation_strategy == 'static': + self.context = self.engine.create_execution_context() + elif allocation_strategy in ['profile', 'runtime']: + # Device memory will be managed by polygraphy + self.context = self.engine.create_execution_context(trt.ExecutionContextAllocationStrategy.USER_MANAGED) + else: + G_LOGGER.critical("Invalid allocation strategy specified.") if not self.context: G_LOGGER.critical("Invalid Context. See error log for details.") elif isinstance(engine_or_context, trt.IExecutionContext): self.context = engine_or_context self.engine = self.context.engine + if self.allocation_strategy is not None: + G_LOGGER.warning( + "An allocation strategy was specified. Please ensure the provided execution context uses the same strategy." + ) + else: G_LOGGER.critical( "Invalid Engine or Context. Please ensure the engine was built correctly. See error log for details." @@ -149,6 +183,7 @@ def activate_impl(self): self.device_input_buffers = OrderedDict() self.host_output_buffers = OrderedDict() self.stream = cuda.Stream() + self.context_memory_buffer = None if self.optimization_profile is not None: self.set_profile(self.optimization_profile) @@ -240,6 +275,20 @@ def get_io(mode): if not self.context.set_output_allocator(name, output_allocator): G_LOGGER.critical(f"For output: {name}, failed to set output allocator") + if self.allocation_strategy in ["profile", "runtime"]: + if self.allocation_strategy == "profile": + # Perform per-profile allocation. + size_to_allocate = self.engine.get_device_memory_size_for_profile(self.context.active_optimization_profile) + elif self.allocation_strategy =="runtime": + # Perform runtime allocation. + size_to_allocate = self.context.update_device_memory_size_for_shapes() + + if self.context_memory_buffer is None: + self.context_memory_buffer = cuda.DeviceArray.raw((size_to_allocate,)) + + self.context_memory_buffer.resize((size_to_allocate,)) + self.context.device_memory = self.context_memory_buffer.ptr + if not self.context.execute_async_v3(self.stream.ptr): G_LOGGER.critical("`execute_async_v3()` failed. Please see the logging output above for details.") @@ -315,6 +364,8 @@ def infer_impl(self, feed_dict, copy_outputs_to_host=None): @util.check_called_by("deactivate") def deactivate_impl(self): [buf.free() for buf in self.device_input_buffers.values()] + if self.context_memory_buffer is not None: + self.context_memory_buffer.free() self.stream.free() del ( @@ -323,4 +374,5 @@ def deactivate_impl(self): self.device_input_buffers, self.host_output_buffers, self.stream, + self.context_memory_buffer, ) diff --git a/tools/Polygraphy/polygraphy/backend/trt/util.py b/tools/Polygraphy/polygraphy/backend/trt/util.py index 0274cb96..ec58d2eb 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/util.py +++ b/tools/Polygraphy/polygraphy/backend/trt/util.py @@ -56,7 +56,9 @@ def log(self, severity, msg): trt.Logger.INTERNAL_ERROR: G_LOGGER.error, trt.Logger.ERROR: G_LOGGER.error, # Reduce warning spam from TRT. - trt.Logger.WARNING: lambda msg: G_LOGGER.warning(msg, mode=LogMode.ONCE), + trt.Logger.WARNING: lambda msg: G_LOGGER.warning( + msg, mode=LogMode.ONCE + ), trt.Logger.INFO: G_LOGGER.verbose, trt.Logger.VERBOSE: G_LOGGER.extra_verbose, }.get(severity, G_LOGGER.super_verbose) @@ -83,7 +85,9 @@ def check_onnx_parser_errors(parser, success): G_LOGGER.critical("Could not parse ONNX correctly") if not success: - G_LOGGER.critical("Failed to parse ONNX model. Does the model file exist and contain a valid ONNX model?") + G_LOGGER.critical( + "Failed to parse ONNX model. Does the model file exist and contain a valid ONNX model?" + ) def get_layer_class_mapping(): @@ -95,7 +99,9 @@ def try_add(layer_type, layer_cls): layer_cls = getattr(trt, layer_cls) except AttributeError: if config.INTERNAL_CORRECTNESS_CHECKS: - G_LOGGER.warning(f"Could not find layer type: {layer_type} or layer class: {layer_cls}") + G_LOGGER.warning( + f"Could not find layer type: {layer_type} or layer class: {layer_cls}" + ) else: layer_class_mapping[layer_type] = layer_cls @@ -158,7 +164,11 @@ def get_network_input_names_meta(network): for i in range(network.num_inputs): tensor = network.get_input(i) names.append(tensor.name) - meta.add(name=tensor.name, dtype=DataType.from_dtype(tensor.dtype, "tensorrt"), shape=tensor.shape) + meta.add( + name=tensor.name, + dtype=DataType.from_dtype(tensor.dtype, "tensorrt"), + shape=tensor.shape, + ) return names, meta @@ -168,7 +178,11 @@ def get_network_output_names_meta(network): for i in range(network.num_outputs): tensor = network.get_output(i) names.append(tensor.name) - meta.add(name=tensor.name, dtype=DataType.from_dtype(tensor.dtype, "tensorrt"), shape=tensor.shape) + meta.add( + name=tensor.name, + dtype=DataType.from_dtype(tensor.dtype, "tensorrt"), + shape=tensor.shape, + ) return names, meta @@ -198,7 +212,14 @@ def str_from_layer(layer, index): input_names, input_meta = get_layer_input_names_meta(layer) output_names, output_meta = get_layer_output_names_meta(layer) return util.str_from_layer( - "Layer", index, layer.name, layer.type, input_names, input_meta, output_names, output_meta + "Layer", + index, + layer.name, + layer.type, + input_names, + input_meta, + output_names, + output_meta, ) @@ -226,7 +247,9 @@ def is_valid_attribute(attr, layer): return [ attr for attr in dir(layer) - if not is_special_attribute(attr) and not hasattr(trt.ILayer, attr) and is_valid_attribute(attr, layer) + if not is_special_attribute(attr) + and not hasattr(trt.ILayer, attr) + and is_valid_attribute(attr, layer) ] @@ -249,13 +272,17 @@ def str_from_network(network, show_layers=None, show_attrs=None, show_weights=No LAYER_TYPE_CLASS_MAPPING = get_layer_class_mapping() - network_str = f"Name: {network.name} | {'Implicit' if hasattr(network, 'has_implicit_batch_dimension') and network.has_implicit_batch_dimension else 'Explicit'} Batch Network{' with Explicit Precision ' if hasattr(network, 'has_explicit_precision') and network.has_explicit_precision else ''}\n" + network_str = f"Name: {network.name} | {'Implicit' if hasattr(network, 'has_implicit_batch_dimension') and network.has_implicit_batch_dimension else 'Explicit'} Batch{' Strongly Typed' if hasattr(network, 'get_flag') and network.get_flag(trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED) else ''} Network\n" network_str += "\n" _, input_metadata = get_network_input_names_meta(network) - network_str += f"---- {len(input_metadata)} Network Input(s) ----\n{input_metadata}\n\n" + network_str += ( + f"---- {len(input_metadata)} Network Input(s) ----\n{input_metadata}\n\n" + ) _, output_metadata = get_network_output_names_meta(network) - network_str += f"---- {len(output_metadata)} Network Output(s) ----\n{output_metadata}\n\n" + network_str += ( + f"---- {len(output_metadata)} Network Output(s) ----\n{output_metadata}\n\n" + ) network_str += f"---- {network.num_layers} Layer(s) ----\n" if show_layers: for index, layer in enumerate(network): @@ -279,7 +306,9 @@ def str_from_network(network, show_layers=None, show_attrs=None, show_weights=No attr_str = "" if layer.name: attr_str += f"{layer.name}." - network_str += util.indent_block(f"{attr_str}{attr} = {val}") + "\n" + network_str += ( + util.indent_block(f"{attr_str}{attr} = {val}") + "\n" + ) network_str += "\n" return util.indent_block(network_str, level=0) @@ -308,7 +337,11 @@ def mark_outputs(network, outputs): tensor_map = get_all_tensors(network) util.check_sequence_contains( - tensor_map.keys(), outputs, name="the network", items_name="outputs", check_extra=False + tensor_map.keys(), + outputs, + name="the network", + items_name="outputs", + check_extra=False, ) for tensor in tensor_map.values(): @@ -325,8 +358,16 @@ def mark_layerwise(network): # Layers within loops cannot be marked as network outputs. LOOP_START_NAMES = ["TRIP_LIMIT", "ITERATOR", "RECURRENCE"] LOOP_END_NAMES = ["LOOP_OUTPUT"] - LOOP_START_LAYERS = [getattr(trt.LayerType, attr) for attr in LOOP_START_NAMES if hasattr(trt.LayerType, attr)] - LOOP_END_LAYERS = [getattr(trt.LayerType, attr) for attr in LOOP_END_NAMES if hasattr(trt.LayerType, attr)] + LOOP_START_LAYERS = [ + getattr(trt.LayerType, attr) + for attr in LOOP_START_NAMES + if hasattr(trt.LayerType, attr) + ] + LOOP_END_LAYERS = [ + getattr(trt.LayerType, attr) + for attr in LOOP_END_NAMES + if hasattr(trt.LayerType, attr) + ] EXCLUDE_LAYERS = [trt.LayerType.SHAPE, trt.LayerType.CONSTANT] outputs = [] in_loop = False @@ -357,7 +398,11 @@ def unmark_outputs(network, outputs): tensor_map = get_all_tensors(network) util.check_sequence_contains( - tensor_map.keys(), outputs, name="the network", items_name="outputs", check_extra=False + tensor_map.keys(), + outputs, + name="the network", + items_name="outputs", + check_extra=False, ) for name in outputs: @@ -382,10 +427,16 @@ def add_line(title, line): def get_enabled_enum_vals(EnumType, is_enabled): # is_enabled is a Callable[[enum_val], bool] which reports whether to include the enum value. - return [name for name, enum_val in EnumType.__members__.items() if is_enabled(enum_val)] + return [ + name + for name, enum_val in EnumType.__members__.items() + if is_enabled(enum_val) + ] # Flags - enabled_builder_flags = get_enabled_enum_vals(trt.BuilderFlag, lambda flag: config.get_flag(flag)) + enabled_builder_flags = get_enabled_enum_vals( + trt.BuilderFlag, lambda flag: config.get_flag(flag) + ) add_line("Flags", f"{str_from_list(enabled_builder_flags)}") # Engine Capability @@ -404,24 +455,35 @@ def get_enabled_enum_vals(EnumType, is_enabled): # Tactic Sources with contextlib.suppress(AttributeError): - source_vals = get_enabled_enum_vals(trt.TacticSource, lambda val: (1 << int(val)) & config.get_tactic_sources()) + source_vals = get_enabled_enum_vals( + trt.TacticSource, lambda val: (1 << int(val)) & config.get_tactic_sources() + ) add_line("Tactic Sources", f"{str_from_list(source_vals)}") # DLA if using_dla: - add_line("DLA", f"Default Device Type: {config.default_device_type}, Core: {config.DLA_core}") + add_line( + "DLA", + f"Default Device Type: {config.default_device_type}, Core: {config.DLA_core}", + ) # Profiling Verbosity with contextlib.suppress(AttributeError): add_line("Profiling Verbosity", f"{config.profiling_verbosity}") # Optimization Profiles - if config.num_optimization_profiles > 1: # Not particularly interesting unless there are multiple. - add_line("Optimization Profiles", f"{config.num_optimization_profiles} profile(s)") + if ( + config.num_optimization_profiles > 1 + ): # Not particularly interesting unless there are multiple. + add_line( + "Optimization Profiles", f"{config.num_optimization_profiles} profile(s)" + ) # Preview Features with contextlib.suppress(AttributeError): - feature_vals = get_enabled_enum_vals(trt.PreviewFeature, lambda val: config.get_preview_feature(val)) + feature_vals = get_enabled_enum_vals( + trt.PreviewFeature, lambda val: config.get_preview_feature(val) + ) if feature_vals: add_line("Preview Features", f"{str_from_list(feature_vals)}") @@ -431,7 +493,9 @@ def get_enabled_enum_vals(EnumType, is_enabled): # Quantization Flags with contextlib.suppress(AttributeError): - quantization_flags = get_enabled_enum_vals(trt.QuantizationFlag, lambda val: config.get_quantization_flag(val)) + quantization_flags = get_enabled_enum_vals( + trt.QuantizationFlag, lambda val: config.get_quantization_flag(val) + ) if quantization_flags: add_line("Quantization Flags", f"{str_from_list(quantization_flags)}") @@ -440,7 +504,9 @@ def get_enabled_enum_vals(EnumType, is_enabled): def check_profile(profile): if not bool(profile): - G_LOGGER.critical(f"Profile is not valid, please provide profile data.\nNote: profile was: {profile}") + G_LOGGER.critical( + f"Profile is not valid, please provide profile data.\nNote: profile was: {profile}" + ) return profile @@ -523,7 +589,8 @@ def try_setup_polygraphy_calibrator(config, network, calib_profile=None): """ calibrator = config.int8_calibrator if calibrator is None or not ( - hasattr(calibrator, "is_polygraphy_calibrator") and calibrator.is_polygraphy_calibrator + hasattr(calibrator, "is_polygraphy_calibrator") + and calibrator.is_polygraphy_calibrator ): # No calibrator or not a Polygraphy calibrator. return @@ -532,13 +599,17 @@ def try_setup_polygraphy_calibrator(config, network, calib_profile=None): try: calib_profile = config.get_calibration_profile() except AttributeError: - G_LOGGER.extra_verbose("Cannot get calibration profile on TensorRT 7.0 and older.") + G_LOGGER.extra_verbose( + "Cannot get calibration profile on TensorRT 7.0 and older." + ) # Return early so we don't emit extraneous warnings on TRT 7.0 and older. return try: # TensorRT does not currently support shapes other than the OPT shape. - input_metadata = get_input_metadata_from_network(network, calib_profile, force_opt_shapes=True) + input_metadata = get_input_metadata_from_network( + network, calib_profile, force_opt_shapes=True + ) except PolygraphyException as err: G_LOGGER.warning( "Could not determine input_metadata to provide to the calibrator because no calibration profile is set. " @@ -581,7 +652,11 @@ def get_metadata_from_engine(engine, context, mode): if get_tensor_format(engine, context, name) == trt.TensorFormat.HWC: shape = get_hwc_shape_from_chw(shape, context.get_tensor_strides(name)) - meta.add(name=name, dtype=DataType.from_dtype(engine.get_tensor_dtype(name), "tensorrt"), shape=shape) + meta.add( + name=name, + dtype=DataType.from_dtype(engine.get_tensor_dtype(name), "tensorrt"), + shape=shape, + ) return meta @@ -595,36 +670,62 @@ def str_from_engine(engine, context, show_layers=None, show_attrs=None): engine_str += "\n" # Show metadata for the first profile (i.e. the dynamic shapes) - input_metadata = get_metadata_from_engine(engine, context, mode=trt.TensorIOMode.INPUT) - output_metadata = get_metadata_from_engine(engine, context, mode=trt.TensorIOMode.OUTPUT) + input_metadata = get_metadata_from_engine( + engine, context, mode=trt.TensorIOMode.INPUT + ) + output_metadata = get_metadata_from_engine( + engine, context, mode=trt.TensorIOMode.OUTPUT + ) - engine_str += f"---- {len(input_metadata)} Engine Input(s) ----\n{input_metadata}\n\n" - engine_str += f"---- {len(output_metadata)} Engine Output(s) ----\n{output_metadata}\n\n" + engine_str += ( + f"---- {len(input_metadata)} Engine Input(s) ----\n{input_metadata}\n\n" + ) + engine_str += ( + f"---- {len(output_metadata)} Engine Output(s) ----\n{output_metadata}\n\n" + ) - engine_str += f"---- Memory ----\nDevice Memory: {engine.device_memory_size} bytes\n\n" + engine_str += ( + f"---- Memory ----\nDevice Memory: {engine.device_memory_size} bytes\n\n" + ) engine_str += f"---- {engine.num_optimization_profiles} Profile(s) ({num_io_tensors} Tensor(s) Each) ----\n" for profile_index in range(engine.num_optimization_profiles): engine_str += f"- Profile: {profile_index}\n" - max_width = max([len(engine.get_tensor_name(idx)) for idx in range(engine.num_io_tensors)]) + 8 + max_width = ( + max( + [ + len(engine.get_tensor_name(idx)) + for idx in range(engine.num_io_tensors) + ] + ) + + 8 + ) for idx in range(num_io_tensors): name = engine.get_tensor_name(idx) - binding_type = " (Input)" if engine.get_tensor_mode(name) == trt.TensorIOMode.INPUT else "(Output)" - engine_str += util.indent_block(f"Tensor: {name:<{max_width}} {binding_type}, Index: {idx}") + binding_type = ( + " (Input)" + if engine.get_tensor_mode(name) == trt.TensorIOMode.INPUT + else "(Output)" + ) + engine_str += util.indent_block( + f"Tensor: {name:<{max_width}} {binding_type}, Index: {idx}" + ) if engine.get_tensor_mode(name) == trt.TensorIOMode.INPUT: - min_shape, opt_shape, max_shape = engine.get_tensor_profile_shape(name, profile_index) - engine_str += f" | Shapes: min={min_shape}, opt={opt_shape}, max={max_shape}\n" + min_shape, opt_shape, max_shape = engine.get_tensor_profile_shape( + name, profile_index + ) + engine_str += ( + f" | Shapes: min={min_shape}, opt={opt_shape}, max={max_shape}\n" + ) else: engine_str += f" | Shape: {engine.get_tensor_shape(name)}\n" engine_str += "\n" layers_per_profile = engine.num_layers // engine.num_optimization_profiles - engine_str += ( - f"---- {layers_per_profile} Layer(s){' Per Profile' if engine.num_optimization_profiles > 1 else ''} ----\n" - ) + engine_str += f"---- {layers_per_profile} Layer(s){' Per Profile' if engine.num_optimization_profiles > 1 else ''} ----\n" if show_layers: try: inspector = engine.create_engine_inspector() @@ -643,7 +744,9 @@ def str_from_engine(engine, context, show_layers=None, show_attrs=None): offset = profile_idx * layers_per_profile for index in range(layers_per_profile): layer_info = json.loads( - inspector.get_layer_information(offset + index, trt.LayerInformationFormat.JSON) + inspector.get_layer_information( + offset + index, trt.LayerInformationFormat.JSON + ) ) op = "Unknown" @@ -672,7 +775,9 @@ def dtype_from_fmt_dtype(contents): for key, val in mapping.items(): if key in contents: return val - G_LOGGER.internal_error(f"Could not determine data type from format string: {contents}") + G_LOGGER.internal_error( + f"Could not determine data type from format string: {contents}" + ) return None names = [] @@ -696,6 +801,10 @@ def dtype_from_fmt_dtype(contents): output_names, output_meta = names_meta_from_inspector("Outputs") origin = layer_info.get("Origin", "Unknown") tactic = layer_info.get("TacticValue", "Unknown") + # For Myelin layers, use `TacticName` instead of `TacticValue` + if "TacticValue" not in layer_info: + tactic = layer_info.get("TacticName", "Unknown") + else: G_LOGGER.warning( f"This engine was created with a profiling verbosity of: {engine.profiling_verbosity}. Some layer information may be missing. Try setting a higher profiling verbosity to see more detailed layer information. ", @@ -706,7 +815,14 @@ def dtype_from_fmt_dtype(contents): engine_str += ( util.indent_block( util.str_from_layer( - "Layer", index, name, op, input_names, input_meta, output_names, output_meta + "Layer", + index, + name, + op, + input_names, + input_meta, + output_names, + output_meta, ), indent_level, ) @@ -714,9 +830,18 @@ def dtype_from_fmt_dtype(contents): ) if show_attrs: - engine_str += util.indent_block("---- Attributes ----", indent_level + 1) + "\n" - engine_str += util.indent_block(f"Origin = {origin}", indent_level + 1) + "\n" - engine_str += util.indent_block(f"Tactic = {tactic}", indent_level + 1) + "\n" + engine_str += ( + util.indent_block("---- Attributes ----", indent_level + 1) + + "\n" + ) + engine_str += ( + util.indent_block(f"Origin = {origin}", indent_level + 1) + + "\n" + ) + engine_str += ( + util.indent_block(f"Tactic = {tactic}", indent_level + 1) + + "\n" + ) engine_str += "\n" diff --git a/tools/Polygraphy/polygraphy/common/struct.py b/tools/Polygraphy/polygraphy/common/struct.py index fcbeaa0f..1e99bcac 100644 --- a/tools/Polygraphy/polygraphy/common/struct.py +++ b/tools/Polygraphy/polygraphy/common/struct.py @@ -92,15 +92,15 @@ def from_feed_dict(feed_dict): Constructs a new TensorMetadata using information from the provided feed_dict. Args: - feed_dict (OrderedDict[str, numpy.ndarray]): - A mapping of input tensor names to corresponding input NumPy arrays. + feed_dict (OrderedDict[str, Union[numpy.ndarray, torch.tensor]]): + A mapping of input tensor names to corresponding input arrays. Returns: TensorMetadata """ meta = TensorMetadata() for name, arr in feed_dict.items(): - meta.add(name, arr.dtype, arr.shape) + meta.add(name, util.array.dtype(arr), util.array.shape(arr)) return meta def add(self, name, dtype, shape, min_shape=None, max_shape=None, docstring=None): diff --git a/tools/Polygraphy/polygraphy/comparator/compare.py b/tools/Polygraphy/polygraphy/comparator/compare.py index b1c2a75a..ec3b5b93 100644 --- a/tools/Polygraphy/polygraphy/comparator/compare.py +++ b/tools/Polygraphy/polygraphy/comparator/compare.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +import copy import functools from collections import OrderedDict @@ -32,7 +33,18 @@ class OutputCompareResult: between two runners. """ - def __init__(self, passed, max_absdiff, max_reldiff, mean_absdiff, mean_reldiff, median_absdiff, median_reldiff, quantile_absdiff, quantile_reldiff): + def __init__( + self, + passed, + max_absdiff, + max_reldiff, + mean_absdiff, + mean_reldiff, + median_absdiff, + median_reldiff, + quantile_absdiff, + quantile_reldiff, + ): """ Records the required tolerances and other statistics gathered during comparison. @@ -89,9 +101,7 @@ def default_find_output_func(output_name, index, iter_result, base_iter_result): G_LOGGER.verbose( f"Will not compare {found_name} with {output_name}, since the former already has an exact match: {exact_match}" ) - return ( - None # If the found output is being compared against another output already, skip this non-exact match - ) + return None # If the found output is being compared against another output already, skip this non-exact match G_LOGGER.warning( f"Output names did not match exactly. Assuming {iter_result.runner_name} output: {found_name} corresponds to output: {output_name}" ) @@ -102,7 +112,9 @@ def run_comparison(func, fail_fast, iter_result0, iter_result1, find_output_func """ Iterates over all the generated outputs and runs `func` to compare them. """ - output_status = OrderedDict() # OrderedDict[str, bool] Maps output names to whether they matched. + output_status = ( + OrderedDict() + ) # OrderedDict[str, bool] Maps output names to whether they matched. for index, (out0_name, output0) in enumerate(iter_result0.items()): out1_names = util.default(find_output_func(out0_name, index, iter_result1), []) @@ -129,11 +141,15 @@ def run_comparison(func, fail_fast, iter_result0, iter_result1, find_output_func if fail_fast and not output_status[out0_name]: return output_status - mismatched_output_names = [name for name, matched in output_status.items() if not matched] + mismatched_output_names = [ + name for name, matched in output_status.items() if not matched + ] if mismatched_output_names: G_LOGGER.error(f"FAILED | Mismatched outputs: {mismatched_output_names}") else: - G_LOGGER.finish(f"PASSED | All outputs matched | Outputs: {list(output_status.keys())}") + G_LOGGER.finish( + f"PASSED | All outputs matched | Outputs: {list(output_status.keys())}" + ) # This is useful for catching cases were Polygraphy does something wrong with the runner output buffers if not output_status and (bool(iter_result0.keys()) or bool(iter_result1.keys())): @@ -168,7 +184,7 @@ def simple( show_heatmaps=None, save_error_metrics_plot=None, show_error_metrics_plot=None, - error_quantile=None + error_quantile=None, ): """ Creates a function that compares two IterationResults, and can be used as the `compare_func` argument @@ -266,7 +282,7 @@ def check_outputs_match( per_out_err_stat, runner0_name, runner1_name, - per_out_quantile + per_out_quantile, ): """ Checks whether two outputs matched. @@ -300,20 +316,27 @@ def check_outputs_match( ) # Check difference vs. tolerances - if util.array.dtype(out0) == DataType.BOOL and util.array.dtype(out1) == DataType.BOOL: + if ( + util.array.dtype(out0) == DataType.BOOL + and util.array.dtype(out1) == DataType.BOOL + ): absdiff = util.array.logical_xor(out0, out1) else: - absdiff = util.array.abs(util.array.subtract(comp_util.cast_up(out0), comp_util.cast_up(out1))) + absdiff = util.array.abs( + util.array.subtract( + comp_util.cast_up(out0), comp_util.cast_up(out1) + ) + ) if infinities_compare_equal: out0_infinite = util.array.isinf(out0) cond = util.array.logical_and(out0_infinite, out0 == out1) absdiff = util.array.where(cond, 0, absdiff) # Add a small epsilon (2e-16) to zero values in the array to prevent NaN in relative error. - cast_up_out1 = comp_util.cast_up(out1) + out1_with_eps = copy.copy(comp_util.cast_up(out1)) - if util.array.dtype(cast_up_out1).is_floating: - if util.array.any(cast_up_out1 == 0): + if util.array.dtype(out1_with_eps).is_floating: + if util.array.any(out1_with_eps == 0): G_LOGGER.warning( f"{runner1_name:35} | Output: {out1_name}: Some values are 0. " f"Will add a small epsilon quantity to these when computing relative difference. " @@ -321,9 +344,10 @@ def check_outputs_match( mode=LogMode.ONCE, ) EPSILON = 2.220446049250313e-16 - cast_up_out1[cast_up_out1 == 0] += EPSILON + out1_with_eps[out1_with_eps == 0] += EPSILON - reldiff = util.array.divide(absdiff, util.array.abs(cast_up_out1)) + # TODO: Only evaluate this if actually needed like we do for quantile_*. + reldiff = util.array.divide(absdiff, util.array.abs(out1_with_eps)) min_reldiff = comp_util.compute_min(reldiff) max_reldiff = comp_util.compute_max(reldiff) mean_reldiff = comp_util.compute_mean(reldiff) @@ -340,39 +364,63 @@ def stat_failed(diff, tol): return util.array.isnan(diff) or diff > tol if per_out_err_stat == "mean": - failed = stat_failed(mean_absdiff, per_out_atol) and stat_failed(mean_reldiff, per_out_rtol) + failed = stat_failed(mean_absdiff, per_out_atol) and stat_failed( + mean_reldiff, per_out_rtol + ) elif per_out_err_stat == "median": - failed = stat_failed(median_absdiff, per_out_atol) and stat_failed(median_reldiff, per_out_rtol) + failed = stat_failed(median_absdiff, per_out_atol) and stat_failed( + median_reldiff, per_out_rtol + ) elif per_out_err_stat == "max": - failed = stat_failed(max_absdiff, per_out_atol) and stat_failed(max_reldiff, per_out_rtol) + failed = stat_failed(max_absdiff, per_out_atol) and stat_failed( + max_reldiff, per_out_rtol + ) elif per_out_err_stat == "quantile": quantile_reldiff = comp_util.compute_quantile(reldiff, per_out_quantile) quantile_absdiff = comp_util.compute_quantile(absdiff, per_out_quantile) - failed = stat_failed(quantile_absdiff, per_out_atol) and stat_failed(quantile_reldiff, per_out_rtol) + failed = stat_failed(quantile_absdiff, per_out_atol) and stat_failed( + quantile_reldiff, per_out_rtol + ) else: assert ( per_out_err_stat == "elemwise" ), "This branch should be unreachable unless per_out_err_stat is 'elemwise'" - mismatches = (util.array.greater(absdiff, per_out_atol) | util.array.isnan(absdiff)) & ( - util.array.greater(reldiff, per_out_rtol) | util.array.isnan(reldiff) + mismatches = ( + util.array.greater(absdiff, per_out_atol) + | util.array.isnan(absdiff) + ) & ( + util.array.greater(reldiff, per_out_rtol) + | util.array.isnan(reldiff) ) failed = util.array.any(mismatches) try: with G_LOGGER.indent(): - G_LOGGER.super_verbose(f"Mismatched indices:\n{util.array.argwhere(mismatches)}") - G_LOGGER.extra_verbose(f"{runner0_name:35} | Mismatched values:\n{out0[mismatches]}") - G_LOGGER.extra_verbose(f"{runner1_name:35} | Mismatched values:\n{out1[mismatches]}") + G_LOGGER.super_verbose( + f"Mismatched indices:\n{util.array.argwhere(mismatches)}" + ) + G_LOGGER.extra_verbose( + f"{runner0_name:35} | Mismatched values:\n{out0[mismatches]}" + ) + G_LOGGER.extra_verbose( + f"{runner1_name:35} | Mismatched values:\n{out1[mismatches]}" + ) except Exception as err: - G_LOGGER.warning(f"Failing to log mismatches.\nNote: Error was: {err}") + G_LOGGER.warning( + f"Failing to log mismatches.\nNote: Error was: {err}" + ) # Log information about the outputs hist_bin_range = ( min(comp_util.compute_min(out0), comp_util.compute_min(out1)), max(comp_util.compute_max(out0), comp_util.compute_max(out1)), ) - comp_util.log_output_stats(out0, failed, f"{runner0_name}: {out0_name}", hist_range=hist_bin_range) - comp_util.log_output_stats(out1, failed, f"{runner1_name}: {out1_name}", hist_range=hist_bin_range) + comp_util.log_output_stats( + out0, failed, f"{runner0_name}: {out0_name}", hist_range=hist_bin_range + ) + comp_util.log_output_stats( + out1, failed, f"{runner1_name}: {out1_name}", hist_range=hist_bin_range + ) G_LOGGER.info(f"Error Metrics: {out0_name}") with G_LOGGER.indent(): @@ -424,13 +472,23 @@ def build_heatmaps(diff, min_diff, max_diff, prefix, use_lognorm=None): build_heatmaps(absdiff, min_absdiff, max_absdiff, "Absolute") comp_util.log_output_stats(reldiff, failed, "Relative Difference") - build_heatmaps(reldiff, min_reldiff, max_reldiff, "Relative", use_lognorm=True) + build_heatmaps( + reldiff, min_reldiff, max_reldiff, "Relative", use_lognorm=True + ) G_LOGGER.extra_verbose( f"Finished comparing: '{out0_name}' (dtype={util.array.dtype(out0)}, shape={util.array.shape(out0)}) [{runner0_name}] and '{out1_name}' (dtype={util.array.dtype(out1)}, shape={util.array.shape(out1)}) [{runner1_name}]" ) return OutputCompareResult( - not failed, max_absdiff, max_reldiff, mean_absdiff, mean_reldiff, median_absdiff, median_reldiff, quantile_absdiff, quantile_reldiff + not failed, + max_absdiff, + max_reldiff, + mean_absdiff, + mean_reldiff, + median_absdiff, + median_reldiff, + quantile_absdiff, + quantile_reldiff, ) def compare_output(iter_result0, iter_result1): @@ -471,43 +529,59 @@ def check_dict(dct, dict_name): check_dict(error_quantile, "the quantile dictionary") if not check_shapes: - G_LOGGER.info("Strict shape checking disabled. Will attempt to match output shapes before comparisons") + G_LOGGER.info( + "Strict shape checking disabled. Will attempt to match output shapes before comparisons" + ) def match(out0_name, output0, out1_name, output1): per_out_atol = util.value_or_from_dict(atol, out0_name, default_atol) per_out_rtol = util.value_or_from_dict(rtol, out0_name, default_rtol) - per_out_err_stat = util.value_or_from_dict(check_error_stat, out0_name, default_error_stat) - per_out_quantile = util.value_or_from_dict(error_quantile, out0_name, default_quantile) + per_out_err_stat = util.value_or_from_dict( + check_error_stat, out0_name, default_error_stat + ) + per_out_quantile = util.value_or_from_dict( + error_quantile, out0_name, default_quantile + ) G_LOGGER.info( f"Tolerance: [abs={per_out_atol:.5g}, rel={per_out_rtol:.5g}] | Checking {per_out_err_stat} error" ) - G_LOGGER.extra_verbose(f"Note: Comparing {iter_result0.runner_name} vs. {iter_result1.runner_name}") + G_LOGGER.extra_verbose( + f"Note: Comparing {iter_result0.runner_name} vs. {iter_result1.runner_name}" + ) - if check_shapes and util.array.shape(output0) != util.array.shape(output1): + if check_shapes and util.array.shape(output0) != util.array.shape( + output1 + ): G_LOGGER.error( - f"Will not compare outputs of different shapes. Note: Output shapes are {util.array.shape(output0)} and {util.array.shape(output1)}." + f"FAILED | Output: `{out0_name}` | Will not compare outputs of different shapes.\n" + f"Note: Output shapes are {util.array.shape(output0)} and {util.array.shape(output1)}." ) G_LOGGER.error( - "Note: Use --no-shape-check or set check_shapes=False to " "attempt to compare values anyway.", + "Note: Use --no-shape-check or set check_shapes=False to " + "attempt to compare values anyway.", mode=LogMode.ONCE, ) - outputs_matched = False - else: - output1 = util.try_match_shape(output1, util.array.shape(output0)) - output0 = util.array.view(output0, DataType.from_dtype(util.array.dtype(output0)), util.array.shape(output1)) - outputs_matched = check_outputs_match( - output0, - out0_name, - output1, - out1_name, - per_out_rtol=per_out_rtol, - per_out_atol=per_out_atol, - per_out_err_stat=per_out_err_stat, - runner0_name=iter_result0.runner_name, - runner1_name=iter_result1.runner_name, - per_out_quantile=per_out_quantile - ) + return False + + output1 = util.try_match_shape(output1, util.array.shape(output0)) + output0 = util.array.view( + output0, + DataType.from_dtype(util.array.dtype(output0)), + util.array.shape(output1), + ) + outputs_matched = check_outputs_match( + output0, + out0_name, + output1, + out1_name, + per_out_rtol=per_out_rtol, + per_out_atol=per_out_atol, + per_out_err_stat=per_out_err_stat, + runner0_name=iter_result0.runner_name, + runner1_name=iter_result1.runner_name, + per_out_quantile=per_out_quantile, + ) # Finally show summary. if not outputs_matched: @@ -523,9 +597,14 @@ def match(out0_name, output0, out1_name, output1): nonlocal find_output_func find_output_func = util.default( - find_output_func, functools.partial(default_find_output_func, base_iter_result=iter_result0) + find_output_func, + functools.partial( + default_find_output_func, base_iter_result=iter_result0 + ), + ) + return run_comparison( + match, fail_fast, iter_result0, iter_result1, find_output_func ) - return run_comparison(match, fail_fast, iter_result0, iter_result1, find_output_func) return compare_output @@ -601,7 +680,9 @@ def compare_output(iter_result0, iter_result1): """ def match(out0_name, output0, out1_name, output1): - per_out_index_tol = util.value_or_from_dict(index_tolerance, out0_name, 0) + per_out_index_tol = util.value_or_from_dict( + index_tolerance, out0_name, 0 + ) if util.array.shape(output0) != util.array.shape(output1): G_LOGGER.error("Cannot compare outputs of different shapes.") @@ -618,9 +699,13 @@ def match(out0_name, output0, out1_name, output1): if val0 == out1_vals[index0]: continue - index1 = util.array.ravel(util.array.argwhere(out1_vals == val0)) + index1 = util.array.ravel( + util.array.argwhere(out1_vals == val0) + ) if util.array.size(index1) < 1: - G_LOGGER.error(f"FAILED | Value: {val0} not found in output") + G_LOGGER.error( + f"FAILED | Value: {val0} not found in output" + ) passed = False if fail_fast: return False @@ -629,7 +714,9 @@ def match(out0_name, output0, out1_name, output1): index1 = index1[0] if abs(index1 - index0) > per_out_index_tol: - G_LOGGER.error(f"FAILED | Difference exceeds index tolerance ({per_out_index_tol})") + G_LOGGER.error( + f"FAILED | Difference exceeds index tolerance ({per_out_index_tol})" + ) passed = False if fail_fast: return False @@ -641,14 +728,22 @@ def match(out0_name, output0, out1_name, output1): max(comp_util.compute_max(output0), comp_util.compute_max(output1)), ) comp_util.log_output_stats( - output0, not passed, f"{iter_result0.runner_name}: {out0_name}", hist_range=hist_bin_range + output0, + not passed, + f"{iter_result0.runner_name}: {out0_name}", + hist_range=hist_bin_range, ) comp_util.log_output_stats( - output1, not passed, f"{iter_result1.runner_name}: {out1_name}", hist_range=hist_bin_range + output1, + not passed, + f"{iter_result1.runner_name}: {out1_name}", + hist_range=hist_bin_range, ) if passed: - G_LOGGER.finish(f"PASSED | Difference is within index tolerance ({per_out_index_tol})") + G_LOGGER.finish( + f"PASSED | Difference is within index tolerance ({per_out_index_tol})" + ) return passed return run_comparison( @@ -656,7 +751,9 @@ def match(out0_name, output0, out1_name, output1): fail_fast, iter_result0, iter_result1, - functools.partial(default_find_output_func, base_iter_result=iter_result0), + functools.partial( + default_find_output_func, base_iter_result=iter_result0 + ), ) return compare_output diff --git a/tools/Polygraphy/polygraphy/comparator/data_loader.py b/tools/Polygraphy/polygraphy/comparator/data_loader.py index 9cab0aff..7dc11cc2 100644 --- a/tools/Polygraphy/polygraphy/comparator/data_loader.py +++ b/tools/Polygraphy/polygraphy/comparator/data_loader.py @@ -25,6 +25,89 @@ from polygraphy.logger import G_LOGGER, LogMode np = mod.lazy_import("numpy") +torch = mod.lazy_import("torch") + + +class ArraySampler: + def __init__(self, data_loader_backend_module, seed): + """ + Args: + data_loader_backend_module (str): + The module specifying the array type to use to generate arrays. + Can be either "numpy" or "torch". + seed (int): + The seed to use when generating random inputs. + """ + self.rng = None + VALID_ARRAY_MODULES = ["numpy", "torch"] + if data_loader_backend_module not in VALID_ARRAY_MODULES: + G_LOGGER.critical( + f"Invalid `data_loader_backend_module`. Note: got: {data_loader_backend_module} but valid modules are: {VALID_ARRAY_MODULES}" + ) + + self.data_loader_backend_module = data_loader_backend_module + + + if self.data_loader_backend_module == "numpy": + self.rng = np.random.RandomState(seed) + elif self.data_loader_backend_module == "torch": + self.rng = torch.Generator() + self.rng.manual_seed(seed) + + + def sample_integer(self, shape, dtype, low, high): + """ + Samples an array containing integral values in the range [low, high], inclusive + """ + dtype = ( + DataType.to_dtype(DataType.from_dtype(dtype), self.data_loader_backend_module) + if dtype is not None + else dtype + ) + if self.data_loader_backend_module == "numpy": + return np.array( + self.rng.randint(low=low, high=high + 1, size=shape, dtype=dtype) + ) + elif self.data_loader_backend_module == "torch": + return torch.randint(low, high + 1, shape, generator=self.rng, dtype=dtype) + + def sample_float(self, shape, dtype, fmin, fmax): + """ + Samples an array containing float values in the range [fmin, fmax], inclusive + """ + # Special handling for infinite lower/upper bounds + # Without this, two infinities will collapse into a NaN, resulting in no infinities + # in the final output. + scale = fmax - fmin + shift = fmin + if util.is_inf(fmin): + scale = fmin + shift = 0 + if util.is_inf(fmax): + scale = fmax + + dtype = ( + DataType.to_dtype(DataType.from_dtype(dtype), self.data_loader_backend_module) + if dtype is not None + else dtype + ) + if self.data_loader_backend_module == "numpy": + return np.array( + (self.rng.random_sample(size=shape) * scale + shift).astype(dtype) + ) + elif self.data_loader_backend_module == "torch": + return torch.rand(shape, generator=self.rng, dtype=dtype) + + def constant_array(self, shape, dtype): + dtype = ( + DataType.to_dtype(DataType.from_dtype(dtype), self.data_loader_backend_module) + if dtype is not None + else dtype + ) + if self.data_loader_backend_module == "numpy": + return np.array(shape, dtype=dtype) + elif self.data_loader_backend_module == "torch": + return torch.tensor(shape, dtype=dtype) @mod.export() @@ -34,7 +117,14 @@ class DataLoader: """ def __init__( - self, seed=None, iterations=None, input_metadata=None, int_range=None, float_range=None, val_range=None + self, + seed=None, + iterations=None, + input_metadata=None, + int_range=None, + float_range=None, + val_range=None, + data_loader_backend_module=None, ): """ Args: @@ -61,6 +151,10 @@ def __init__( This can be specified on a per-input basis using a dictionary. In that case, use an empty string ("") as the key to specify default range for inputs not explicitly listed. Defaults to (0.0, 1.0). + data_loader_backend_module (str): + A string denoting what module to use to construct the input data arrays. Currently supports + "numpy" and "torch". + Defaults to "numpy". int_range (Tuple[int]): [DEPRECATED - Use val_range instead] @@ -79,7 +173,9 @@ def __init__( """ def default_tuple(tup, default): - if tup is None or (not isinstance(tup, tuple) and not isinstance(tup, list)): + if tup is None or ( + not isinstance(tup, tuple) and not isinstance(tup, list) + ): return default new_tup = [] for elem, default_elem in zip(tup, default): @@ -89,15 +185,24 @@ def default_tuple(tup, default): self.seed = util.default(seed, constants.DEFAULT_SEED) self.iterations = util.default(iterations, 1) self.user_input_metadata = util.default(input_metadata, {}) + self.data_loader_backend_module = util.default( + data_loader_backend_module, "numpy" + ) self._int_range_set = int_range is not None if self._int_range_set: - mod.warn_deprecated("The int_range parameter in DataLoader", "val_range", remove_in="0.50.0") + mod.warn_deprecated( + "The int_range parameter in DataLoader", "val_range", remove_in="0.50.0" + ) self._int_range = default_tuple(int_range, (1, 25)) self._float_range_set = float_range is not None if self._float_range_set: - mod.warn_deprecated("The float_range parameter in DataLoader", "val_range", remove_in="0.50.0") + mod.warn_deprecated( + "The float_range parameter in DataLoader", + "val_range", + remove_in="0.50.0", + ) self._float_range = default_tuple(float_range, (-1.0, 1.0)) self.input_metadata = None @@ -118,6 +223,7 @@ def __repr__(self): int_range=self._int_range, float_range=self._float_range, val_range=self.val_range, + data_loader_backend_module=self.data_loader_backend_module, )[0] def _get_range(self, name, cast_type): @@ -141,13 +247,14 @@ def __getitem__(self, index): Generated data is guaranteed to be the same for the same index. Returns: - OrderedDict[str, numpy.ndarray]: A mapping of input names to input numpy buffers. + OrderedDict[str, Union[numpy.ndarray, torch.Tensor]]: A mapping of input names to input numpy buffers. """ if index >= self.iterations: raise IndexError() G_LOGGER.verbose(f"Generating data using numpy seed: {self.seed + index}") - rng = np.random.RandomState(self.seed + index) + + array_sampler = ArraySampler(self.data_loader_backend_module, self.seed + index) def get_static_shape(name, shape): static_shape = shape @@ -182,7 +289,11 @@ def is_shape_tensor(name, dtype): return False _, shape = self.input_metadata[name] - if not np.issubdtype(dtype, np.integer) or util.is_shape_dynamic(shape) or len(shape) != 1: + if ( + (dtype is not None and not DataType.from_dtype(dtype).is_integral) + or util.is_shape_dynamic(shape) + or len(shape) != 1 + ): return False user_shape = self.user_input_metadata[name].shape @@ -191,20 +302,26 @@ def is_shape_tensor(name, dtype): def generate_buffer(name, dtype, shape): if is_shape_tensor(name, dtype): - buffer = np.array(shape, dtype=dtype) + buffer = array_sampler.constant_array(shape, dtype) G_LOGGER.info( f"Assuming {name} is a shape tensor. Setting input values to: {buffer}. " "If these values are not correct, please set it correctly in 'input_metadata' or by providing --input-shapes", mode=LogMode.ONCE, ) - elif np.issubdtype(dtype, np.integer) or np.issubdtype(dtype, np.bool_): - imin, imax = self._get_range(name, cast_type=int if np.issubdtype(dtype, np.integer) else bool) + elif dtype is not None and ( + DataType.from_dtype(dtype).is_integral + or DataType.from_dtype(dtype) == DataType.BOOL + ): + imin, imax = self._get_range( + name, + cast_type=int if DataType.from_dtype(dtype).is_integral else bool, + ) G_LOGGER.verbose( f"Input tensor: {name} | Generating input data in range: [{imin}, {imax}]", mode=LogMode.ONCE, ) # high is 1 greater than the max int drawn. - buffer = rng.randint(low=imin, high=imax + 1, size=shape, dtype=dtype) + buffer = array_sampler.sample_integer(shape, dtype, imin, imax) else: fmin, fmax = self._get_range(name, cast_type=float) G_LOGGER.verbose( @@ -212,20 +329,8 @@ def generate_buffer(name, dtype, shape): mode=LogMode.ONCE, ) - # Special handling for infinite lower/upper bounds - # Without this, two inifinities will collapse into a NaN, resulting in no inifinities - # in the final output. - scale = fmax - fmin - shift = fmin - if util.is_inf(fmin): - scale = fmin - shift = 0 - if util.is_inf(fmax): - scale = fmax + buffer = array_sampler.sample_float(shape, dtype, fmin, fmax) - buffer = (rng.random_sample(size=shape) * scale + shift).astype(dtype) - - buffer = np.array(buffer) # To handle scalars, since the above functions return a float if shape is (). return buffer if self.input_metadata is None and self.user_input_metadata is not None: @@ -234,18 +339,33 @@ def generate_buffer(name, dtype, shape): buffers = OrderedDict() for name, (dtype, shape) in self.input_metadata.items(): try: - dtype = DataType.to_dtype(DataType.from_dtype(dtype), "numpy") if dtype is not None else None + dtype = ( + DataType.to_dtype( + DataType.from_dtype(dtype), self.data_loader_backend_module + ) + if dtype is not None + else None + ) except DataTypeConversionException: G_LOGGER.critical( - f"Could not convert data type: {dtype} to NumPy, so the default data loader cannot generate a NumPy array for input: {name}. " + f"Could not convert data type: {dtype} to {self.data_loader_backend_module}, so the default data loader cannot generate a {self.data_loader_backend_module} array for input: {name}. " f"Please use a custom data loader to provide inputs. " ) if name in self.user_input_metadata: user_dtype, user_shape = self.user_input_metadata[name] dtype = util.default(user_dtype, dtype) - dtype = DataType.to_dtype(DataType.from_dtype(dtype), "numpy") if dtype is not None else None - is_valid_shape_override = user_shape is not None and util.is_valid_shape_override(user_shape, shape) + dtype = ( + DataType.to_dtype( + DataType.from_dtype(dtype), self.data_loader_backend_module + ) + if dtype is not None + else None + ) + is_valid_shape_override = ( + user_shape is not None + and util.is_valid_shape_override(user_shape, shape) + ) if util.is_shape_dynamic(user_shape): G_LOGGER.warning( @@ -266,7 +386,9 @@ def generate_buffer(name, dtype, shape): for name in self.user_input_metadata.keys(): if name not in self.input_metadata: msg = f"Input tensor: {name} | Metadata was provided, but the input does not exist in one or more runners." - close_match = util.find_str_in_iterable(name, self.input_metadata.keys()) + close_match = util.find_str_in_iterable( + name, self.input_metadata.keys() + ) if close_match: msg += f"\nMaybe you meant to set: {close_match}?" G_LOGGER.warning(msg) @@ -305,9 +427,13 @@ def __getitem__(self, iteration): # Attempts to match existing input buffers to the requested input_metadata def coerce_cached_input(index, name, dtype, shape): cached_feed_dict = self.cache[iteration] - cached_name = util.find_str_in_iterable(name, cached_feed_dict.keys(), index) + cached_name = util.find_str_in_iterable( + name, cached_feed_dict.keys(), index + ) if cached_name is None: - G_LOGGER.critical(f"Input tensor: {name} | Does not exist in the data loader cache.") + G_LOGGER.critical( + f"Input tensor: {name} | Does not exist in the data loader cache." + ) if cached_name != name: G_LOGGER.warning( @@ -332,7 +458,9 @@ def coerce_cached_input(index, name, dtype, shape): elif dtype.is_floating: type_info = np.finfo(np_type) - if type_info is not None and util.array.any((buffer < type_info.min) | (buffer > type_info.max)): + if type_info is not None and util.array.any( + (buffer < type_info.min) | (buffer > type_info.max) + ): G_LOGGER.warning( f"Some values in this input are out of range of {dtype}. Unexpected behavior may ensue!" ) @@ -345,7 +473,9 @@ def coerce_cached_input(index, name, dtype, shape): ) buffer = util.try_match_shape(buffer, shape) - if util.array.dtype(buffer) != dtype or not util.is_valid_shape_override(util.array.shape(buffer), shape): + if util.array.dtype(buffer) != dtype or not util.is_valid_shape_override( + util.array.shape(buffer), shape + ): G_LOGGER.critical( f"Input tensor: {name} | Cannot reuse input data due to mismatch in shape or data type.\n" f"Note: Cached input: [dtype={util.array.dtype(buffer)}, shape={util.array.shape(buffer)}], " @@ -360,7 +490,9 @@ def coerce_cached_input(index, name, dtype, shape): for index, (name, (dtype, shape)) in enumerate(self.input_metadata.items()): try: - buffer = coerce_cached_input(index, name, DataType.from_dtype(dtype), shape) + buffer = coerce_cached_input( + index, name, DataType.from_dtype(dtype), shape + ) except PolygraphyException: G_LOGGER.warning( f"Could not use buffer previously cached from data loader for input: {name}. Attempting to reload inputs from the data loader.\nNote that this will only work if the data loader supports random access.\nPlease refer to warnings above for details on why the previously generated input buffer didn't work. " diff --git a/tools/Polygraphy/polygraphy/datatype/datatype.py b/tools/Polygraphy/polygraphy/datatype/datatype.py index a8314d82..c39a3b38 100644 --- a/tools/Polygraphy/polygraphy/datatype/datatype.py +++ b/tools/Polygraphy/polygraphy/datatype/datatype.py @@ -90,6 +90,7 @@ class DataType: "INT32": DataTypeEntry("int32", 4, _DataTypeKind.INTEGRAL), "INT64": DataTypeEntry("int64", 8, _DataTypeKind.INTEGRAL), "INT8": DataTypeEntry("int8", 1, _DataTypeKind.INTEGRAL), + "INT4": DataTypeEntry("int4", 0.5, _DataTypeKind.INTEGRAL), "UINT16": DataTypeEntry("uint16", 2, _DataTypeKind.INTEGRAL), "UINT32": DataTypeEntry("uint32", 4, _DataTypeKind.INTEGRAL), "UINT64": DataTypeEntry("uint64", 8, _DataTypeKind.INTEGRAL), @@ -98,9 +99,13 @@ class DataType: "STRING": DataTypeEntry("string", 0, _DataTypeKind._OTHER), "BFLOAT16": DataTypeEntry("bfloat16", 2, _DataTypeKind.FLOATING_POINT), "FLOAT8E4M3FN": DataTypeEntry("float8e4m3fn", 1, _DataTypeKind.FLOATING_POINT), - "FLOAT8E4M3FNUZ": DataTypeEntry("float8e4m3fnuz", 1, _DataTypeKind.FLOATING_POINT), + "FLOAT8E4M3FNUZ": DataTypeEntry( + "float8e4m3fnuz", 1, _DataTypeKind.FLOATING_POINT + ), "FLOAT8E5M2": DataTypeEntry("float8e5m2", 1, _DataTypeKind.FLOATING_POINT), - "FLOAT8E5M2FNUZ": DataTypeEntry("float8e5m2fnuz", 1, _DataTypeKind.FLOATING_POINT), + "FLOAT8E5M2FNUZ": DataTypeEntry( + "float8e5m2fnuz", 1, _DataTypeKind.FLOATING_POINT + ), } @staticmethod @@ -168,7 +173,9 @@ def to_dtype(dtype, target_module): PolygraphyException: If the data type could not be converted. """ if not isinstance(dtype, DataTypeEntry): - G_LOGGER.internal_error(f"Received input of type other than DataType: {dtype}") + G_LOGGER.internal_error( + f"Received input of type other than DataType: {dtype}" + ) return dtype if target_module not in DataType._EXPORTER_FUNCS: @@ -190,7 +197,8 @@ def register_dtype_importer(source_module): Registers an importer function with the DataType class. IMPORTANT: You *must* ensure that the importer function does not attempt to automatically install - or import modules which are not already installed. `mod.has_mod` is an easy way to guard the code against this. + or import modules which are not already installed. + With a lazily imported module, `module.is_installed()/is_importable()` is an easy way to guard the code against this. We do not want to automatically install heavy modules like PyTorch or TensorRT just for the sake of DataType. For example: diff --git a/tools/Polygraphy/polygraphy/datatype/numpy.py b/tools/Polygraphy/polygraphy/datatype/numpy.py index 9dea2d70..5f6fedc5 100644 --- a/tools/Polygraphy/polygraphy/datatype/numpy.py +++ b/tools/Polygraphy/polygraphy/datatype/numpy.py @@ -51,7 +51,7 @@ def from_numpy(numpy_type): Returns: DataType: The Polygraphy data type. """ - if not mod.has_mod("numpy"): + if not np.is_installed() or not np.is_importable(): return None try: diff --git a/tools/Polygraphy/polygraphy/datatype/onnx.py b/tools/Polygraphy/polygraphy/datatype/onnx.py index 19322ad0..4fc66579 100644 --- a/tools/Polygraphy/polygraphy/datatype/onnx.py +++ b/tools/Polygraphy/polygraphy/datatype/onnx.py @@ -60,7 +60,7 @@ def from_onnx(onnx_type): Returns: DataType: The Polygraphy data type. """ - if not mod.has_mod("onnx"): + if not onnx.is_installed() or not onnx.is_importable(): return None return _get_mapping().get(onnx_type) diff --git a/tools/Polygraphy/polygraphy/datatype/tensorrt.py b/tools/Polygraphy/polygraphy/datatype/tensorrt.py index 63d913f8..ad327e96 100644 --- a/tools/Polygraphy/polygraphy/datatype/tensorrt.py +++ b/tools/Polygraphy/polygraphy/datatype/tensorrt.py @@ -16,7 +16,11 @@ # from polygraphy import mod, util -from polygraphy.datatype.datatype import DataType, register_dtype_importer, register_dtype_exporter +from polygraphy.datatype.datatype import ( + DataType, + register_dtype_importer, + register_dtype_exporter, +) trt = mod.lazy_import("tensorrt>=8.5") @@ -32,6 +36,7 @@ def _get_mapping(): util.try_getattr(trt, "bool"): DataType.BOOL, util.try_getattr(trt, "bfloat16"): DataType.BFLOAT16, util.try_getattr(trt, "fp8"): DataType.FLOAT8E4M3FN, + util.try_getattr(trt, "int4"): DataType.INT4, } if None in DATATYPE_FROM_TENSORRT: del DATATYPE_FROM_TENSORRT[None] @@ -50,7 +55,7 @@ def from_tensorrt(tensorrt_type): Returns: DataType: The Polygraphy data type. """ - if not mod.has_mod("tensorrt"): + if not trt.is_installed() or not trt.is_importable(): return None return _get_mapping().get(tensorrt_type) diff --git a/tools/Polygraphy/polygraphy/datatype/torch.py b/tools/Polygraphy/polygraphy/datatype/torch.py index 2e3d8047..cf217d36 100644 --- a/tools/Polygraphy/polygraphy/datatype/torch.py +++ b/tools/Polygraphy/polygraphy/datatype/torch.py @@ -47,7 +47,7 @@ def from_torch(torch_type): Returns: DataType: The Polygraphy data type. """ - if not mod.has_mod("torch"): + if not torch.is_installed() or not torch.is_importable(): return None return _get_mapping().get(torch_type) diff --git a/tools/Polygraphy/polygraphy/json/serde.py b/tools/Polygraphy/polygraphy/json/serde.py index bffd448b..fcc73180 100644 --- a/tools/Polygraphy/polygraphy/json/serde.py +++ b/tools/Polygraphy/polygraphy/json/serde.py @@ -186,7 +186,9 @@ def __call__(self, pairs): "Tensor": "torch.Tensor", "ndarray": "np.ndarray", }.get(type_name, type_name) - G_LOGGER.critical(f"Could not decode serialized type: {user_type_name}. This could be because a required module is missing. ") + G_LOGGER.critical( + f"Could not decode serialized type: {user_type_name}. This could be because a required module is missing. " + ) return self.polygraphy_registered[type_name](dct) return dct @@ -209,7 +211,7 @@ def try_register_common_json(func): @functools.wraps(func) def wrapped(*args, **kwargs): global NUMPY_REGISTRATION_SUCCESS - if not NUMPY_REGISTRATION_SUCCESS and mod.has_mod("numpy"): + if not NUMPY_REGISTRATION_SUCCESS and np.is_installed() and np.is_importable(): # We define this alongside load_json/save_json so that it is guaranteed to be # imported before we need to encode/decode NumPy arrays. @Encoder.register(np.ndarray) @@ -243,7 +245,7 @@ def load(mode="base64"): NUMPY_REGISTRATION_SUCCESS = True global TORCH_REGISTRATION_SUCCESS - if not TORCH_REGISTRATION_SUCCESS and mod.has_mod("torch"): + if not TORCH_REGISTRATION_SUCCESS and torch.is_installed() and torch.is_importable(): @Encoder.register(torch.Tensor) def encode(tensor): diff --git a/tools/Polygraphy/polygraphy/mod/importer.py b/tools/Polygraphy/polygraphy/mod/importer.py index 1ef6385c..c686653c 100644 --- a/tools/Polygraphy/polygraphy/mod/importer.py +++ b/tools/Polygraphy/polygraphy/mod/importer.py @@ -23,6 +23,12 @@ import sys from typing import List +try: + # Available in Python 3.8+ + import importlib.metadata +except ModuleNotFoundError: + pass + from polygraphy import constants from polygraphy.mod import util as mod_util @@ -95,13 +101,27 @@ def lazy_import( A lazily loaded module. When an attribute is first accessed, the module will be imported. """ + + def issue_wrong_version_error(installed_version, version): + from polygraphy.logger import G_LOGGER, LogMode + + G_LOGGER.error( + f"Module: '{name}' version '{installed_version}' is installed, but version '{version}' is required.\n" + f"Please install the required version or set POLYGRAPHY_AUTOINSTALL_DEPS=1 in your environment variables " + f"to allow Polygraphy to do so automatically.\n" + f"Attempting to continue with the currently installed version of this module, but note that this may cause errors!", + mode=LogMode.ONCE, + ) + VERSION_CHARS = ["=", ">", "<"] log = True if log is None else log requires = [] if requires is None else requires def split_name_version(inp): - version_char_indices = [inp.index(char) for char in VERSION_CHARS if char in inp] + version_char_indices = [ + inp.index(char) for char in VERSION_CHARS if char in inp + ] if not version_char_indices: return inp, None @@ -120,17 +140,29 @@ def import_mod(): def install_mod(install_name, install_version, raise_error=True): modname = install_name.split(".")[0] - pkg = pkg_name if pkg_name is not None else _PKG_NAME_FROM_MODULE.get(modname, modname) - extra_flags = install_flags if install_flags is not None else _EXTRA_FLAGS_FOR_MODULE.get(modname, []) + pkg = ( + pkg_name + if pkg_name is not None + else _PKG_NAME_FROM_MODULE.get(modname, modname) + ) + extra_flags = ( + install_flags + if install_flags is not None + else _EXTRA_FLAGS_FOR_MODULE.get(modname, []) + ) def fail(): log_func = G_LOGGER.critical if raise_error else G_LOGGER.warning - log_func(f"Could not automatically install required module: {pkg}. Please install it manually.") + log_func( + f"Could not automatically install required module: {pkg}. Please install it manually." + ) if config.ASK_BEFORE_INSTALL: res = None while res not in ["y", "n"]: - res = input(f"Automatically install '{pkg}' (version: {install_version or 'any'}) ([Y]/n)? ") + res = input( + f"Automatically install '{pkg}' (version: {install_version or 'any'}) ([Y]/n)? " + ) res = res.strip()[:1].lower() or "y" if res == "n": @@ -145,7 +177,9 @@ def fail(): G_LOGGER.info(f"Running installation command: {' '.join(cmd)}") status = sp.run(cmd, stdout=sp.PIPE, stderr=sp.PIPE) if status.returncode != 0: - G_LOGGER.error(f"Error during installation:\n{constants.TAB}{status.stderr.decode()}") + G_LOGGER.error( + f"Error during installation:\n{constants.TAB}{status.stderr.decode()}" + ) fail() mod = importlib.import_module(install_name) @@ -182,17 +216,15 @@ def fail(): f"Attempting to upgrade now." ) # We can try to use the other version if install fails, so this is non-fatal. - installed_mod = install_mod(install_name, install_version, raise_error=False) + installed_mod = install_mod( + install_name, install_version, raise_error=False + ) if install_name == name: mod = installed_mod elif install_version != LATEST_VERSION: - G_LOGGER.error( - f"Module: '{install_name}' version '{installed_mod.__version__}' is installed, but version '{install_version}' is required.\n" - f"Please install the required version or set POLYGRAPHY_AUTOINSTALL_DEPS=1 in your environment variables " - f"to allow Polygraphy to do so automatically.\n" - f"Attempting to continue with the currently installed version of this module, but note that this may cause errors!", - mode=LogMode.ONCE, + issue_wrong_version_error( + installed_mod.__version__, install_version ) if log: @@ -219,12 +251,42 @@ def __setattr__(self, name, value): module = self.__polygraphy_import_mod() return setattr(module, name, value) + def is_installed(self): + """ + Checks whether any version of this module is installed. + The module will not be imported by this method. + + Returns: + bool: Whether the module is installed. + """ + global importlib + + try: + return name in sys.modules or ( + importlib.util.find_spec(name) is not None + ) + except: + return False + + def is_importable(self): + """ + Checks whether this module is importable. Note that a module may be installed but not importable. + + Returns: + bool: Whether the module is importable. + """ + try: + importlib.import_module(name) + return True + except: + return False + return LazyModule() def has_mod(modname): """ - Checks whether a module is installed. + Checks whether a module is installed without importing the module. Args: modname (str): The name of the module to check. @@ -232,6 +294,22 @@ def has_mod(modname): Returns: bool: Whether the module is installed. """ + import warnings + + import polygraphy + from polygraphy.logger import G_LOGGER + + remove_in = "0.50.0" + if mod_util.version(polygraphy.__version__) >= mod_util.version(remove_in): + G_LOGGER.internal_error( + f"has_mod should have been removed in version: {remove_in}" + ) + warnings.warn( + f"has_mod is deprecated and will be removed in Polygraphy {remove_in}", + DeprecationWarning, + stacklevel=3, + ) + try: return modname in sys.modules or (importlib.util.find_spec(modname) is not None) except ValueError: @@ -292,9 +370,7 @@ def reset_sys_path(): ext = os.path.splitext(path)[1] err_msg = f"Could not import symbol: {name} from script: {path}" if ext != ".py": - err_msg += ( - f"\nThis could be because the extension of the file is not '.py'. Note: The extension is: {ext}" - ) + err_msg += f"\nThis could be because the extension of the file is not '.py'. Note: The extension is: {ext}" err_msg += f"\nNote: Error was: {err}" err_msg += f"\nNote: sys.path was: {sys.path}" G_LOGGER.critical(err_msg) diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/trt/config.py b/tools/Polygraphy/polygraphy/tools/args/backend/trt/config.py index 6938bc0f..51f287e3 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/trt/config.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/trt/config.py @@ -319,6 +319,12 @@ def add_parser_args_impl(self): action="store_true", default=None, ) + self.group.add_argument( + "--strip-plan", + help="Builds the engine with the refittable weights stripped.", + action="store_true", + default=None, + ) self.group.add_argument( "--use-dla", help="[EXPERIMENTAL] Use DLA as the default device type", @@ -394,6 +400,22 @@ def add_parser_args_impl(self): default=None, ) + self.group.add_argument( + "--profiling-verbosity", + help="The verbosity of NVTX annotations in the generated engine." + "Values come from the names of values in the `trt.ProfilingVerbosity` enum and are case-insensitive. " + "For example, `--profiling-verbosity detailed`. " + "Defaults to 'verbose'.", + default=None, + ) + + self.group.add_argument( + "--weight-streaming", + help="Build a weight streamable engine. Must be set with --strongly-typed. The weight streaming amount can be set with --weight-streaming-budget.", + action="store_true", + default=None + ) + if self._allow_engine_capability: self.group.add_argument( "--engine-capability", @@ -443,14 +465,17 @@ def parse_impl(self, args): direct_io (bool): Whether to disallow reformatting layers at network input/output tensors which have user-specified formats. preview_features (List[str]): Names of preview features to enable. refittable (bool): Whether the engine should be refittable. + strip_plan (bool): Whether the engine should be built with the refittable weights stripped. builder_optimization_level (int): The builder optimization level. hardware_compatibility_level (str): A string representing a hardware compatibility level enum value. + profiling_verbosity (str): A string representing a profiling verbosity level enum value. max_aux_streams (int): The maximum number of auxiliary streams that TensorRT is allowed to use. version_compatible (bool): Whether or not to build a TensorRT forward-compatible. exclude_lean_runtime (bool): Whether to exclude the lean runtime from a version compatible plan. quantization_flags (List[str]): Names of quantization flags to enable. error_on_timing_cache_miss (bool): Whether to emit error when a tactic being timed is not present in the timing cache. disable_compilation_cache (bool): Whether to disable caching JIT-compiled code. + weight_streaming (bool): Whether to enable weight streaming for the TensorRT Engine. """ trt_min_shapes = args_util.get(args, "trt_min_shapes", default=[]) @@ -477,6 +502,7 @@ def parse_impl(self, args): self.restricted = args_util.get(args, "restricted") self.refittable = args_util.get(args, "refittable") + self.strip_plan = args_util.get(args, "strip_plan") self.calibration_cache = args_util.get(args, "calibration_cache") calib_base = args_util.get(args, "calibration_base_class") @@ -549,6 +575,13 @@ def parse_impl(self, args): "HardwareCompatibilityLevel", hardware_compatibility_level ) + self.profiling_verbosity = None + profiling_verbosity = args_util.get(args, "profiling_verbosity") + if profiling_verbosity is not None: + self.profiling_verbosity = make_trt_enum_val( + "ProfilingVerbosity", profiling_verbosity + ) + self.max_aux_streams = args_util.get(args, "max_aux_streams") self.version_compatible = args_util.get(args, "version_compatible") self.exclude_lean_runtime = args_util.get(args, "exclude_lean_runtime") @@ -565,6 +598,8 @@ def parse_impl(self, args): self.disable_compilation_cache = args_util.get(args, "disable_compilation_cache") + self.weight_streaming = args_util.get(args, "weight_streaming") + def add_to_script_impl(self, script): profiles = [] for profile_dict in self.profile_dicts: @@ -632,10 +667,9 @@ def add_to_script_impl(self, script): self.memory_pool_limits, self.preview_features, self.engine_capability, + self.profiling_verbosity, self.hardware_compatibility_level, self.quantization_flags, - self.error_on_timing_cache_miss, - self.disable_compilation_cache, ] ): script.add_import(imports="tensorrt", imp_as="trt") @@ -665,17 +699,20 @@ def add_to_script_impl(self, script): allow_gpu_fallback=self.allow_gpu_fallback, memory_pool_limits=self.memory_pool_limits, refittable=self.refittable, + strip_plan=self.strip_plan, preview_features=self.preview_features, engine_capability=self.engine_capability, direct_io=self.direct_io, builder_optimization_level=self.builder_optimization_level, hardware_compatibility_level=self.hardware_compatibility_level, + profiling_verbosity=self.profiling_verbosity, max_aux_streams=self.max_aux_streams, version_compatible=self.version_compatible, exclude_lean_runtime=self.exclude_lean_runtime, quantization_flags=self.quantization_flags, error_on_timing_cache_miss=self.error_on_timing_cache_miss, disable_compilation_cache=self.disable_compilation_cache, + weight_streaming=self.weight_streaming, ) if config_loader_str is not None: script.add_import(imports="CreateConfig", frm="polygraphy.backend.trt", imp_as="CreateTrtConfig") diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/trt/loader.py b/tools/Polygraphy/polygraphy/tools/args/backend/trt/loader.py index f6dc68e0..279527e3 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/trt/loader.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/trt/loader.py @@ -80,6 +80,14 @@ def add_parser_args_impl(self): nargs="+", default=None, ) + self.group.add_argument( + "--plugin-instancenorm", + help="Switch to clear the `trt.OnnxParserFlag.NATIVE_INSTANCENORM` flag and" + "force the usage of the plugin implementation of ONNX InstanceNorm." + "Note that `trt.OnnxParserFlag.NATIVE_INSTANCENORM` is ON by default since TensorRT 10.0.", + action="store_true", + default=None, + ) def parse_impl(self, args): """ @@ -89,6 +97,7 @@ def parse_impl(self, args): flags (List[str]): flags for onnxparser """ self._flags = args_util.get(args, "onnx_flags", default=[]) + self._plugin_instancenorm = args_util.get(args, "plugin_instancenorm", default=None) def get_flags(self): """ @@ -110,7 +119,7 @@ def get_flags(self): ) flags.append("native_instancenorm") - return [make_trt_enum_val("OnnxParserFlag", f) for f in flags] or None + return ([make_trt_enum_val("OnnxParserFlag", f) for f in flags] or None, self._plugin_instancenorm) @mod.export() @@ -291,10 +300,10 @@ def add_to_script_impl(self, script): model_file = self.arg_groups[ModelArgs].path model_type = self.arg_groups[ModelArgs].model_type outputs = args_util.get_outputs_for_script(script, self.outputs) - parser_flags = self.arg_groups[TrtOnnxFlagArgs].get_flags() + parser_flags, plugin_instancenorm = self.arg_groups[TrtOnnxFlagArgs].get_flags() if any( - arg is not None for arg in [self.layer_precisions, self.tensor_datatypes, self.tensor_formats, parser_flags] + arg is not None for arg in [self.layer_precisions, self.tensor_datatypes, self.tensor_formats, parser_flags, plugin_instancenorm] ): script.add_import(imports="tensorrt", imp_as="trt") @@ -318,6 +327,7 @@ def add_to_script_impl(self, script): "NetworkFromOnnxBytes", self.arg_groups[TrtLoadPluginsArgs].add_to_script(script, onnx_loader), flags=parser_flags, + plugin_instancenorm=plugin_instancenorm, strongly_typed=self.strongly_typed, ) loader_name = script.add_loader(loader_str, "parse_network_from_onnx") @@ -327,6 +337,7 @@ def add_to_script_impl(self, script): "NetworkFromOnnxPath", self.arg_groups[TrtLoadPluginsArgs].add_to_script(script, model_file), flags=parser_flags, + plugin_instancenorm=plugin_instancenorm, strongly_typed=self.strongly_typed, ) loader_name = script.add_loader(loader_str, "parse_network_from_onnx") diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/trt/runner.py b/tools/Polygraphy/polygraphy/tools/args/backend/trt/runner.py index 7f5632f9..8ba25942 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/trt/runner.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/trt/runner.py @@ -42,6 +42,27 @@ def add_parser_args_impl(self): default=None, dest="optimization_profile", ) + self.group.add_argument( + "--allocation-strategy", + help="The way activation memory is allocated. " + "static: Pre-allocate based on the max possible size across all profiles. " + "profile: Allocate what's needed for the profile to use." + "runtime: Allocate what's needed for the current input shapes.", + type=str, + default=None, + dest="allocation_strategy", + choices=["static", "profile", "runtime"], + ) + self.group.add_argument( + "--weight-streaming-budget", + help="The amount of GPU memory in bytes that TensorRT can use for weights at runtime. The engine must be built with weight streaming enabled. It can take on the following values: " + ' None: TensorRT will decide the streaming budget automatically.' + ' -1: Disables weight streaming at runtime.' + ' 0: Sets weight streaming budget to minimum budget.' + ' >0: The amount of memory in bytes TensorRT can use for weights.', + type=int, + default=None + ) def parse_impl(self, args): """ @@ -49,10 +70,14 @@ def parse_impl(self, args): Attributes: optimization_profile (int): The index of the optimization profile to initialize the runner with. + allocation_strategy (str): The way activation memory is allocated. + weight_streaming_budget (int): The weight streaming budget in bytes. """ self.optimization_profile = args_util.get(args, "optimization_profile") + self.allocation_strategy = args_util.get(args, "allocation_strategy") + self.weight_streaming_budget = args_util.parse_num_bytes(args_util.get(args, "weight_streaming_budget")) def add_to_script_impl(self, script): script.add_import(imports=["TrtRunner"], frm="polygraphy.backend.trt") loader_name = self.arg_groups[TrtLoadEngineArgs].add_to_script(script) - script.add_runner(make_invocable("TrtRunner", loader_name, optimization_profile=self.optimization_profile)) + script.add_runner(make_invocable("TrtRunner", loader_name, optimization_profile=self.optimization_profile, allocation_strategy=self.allocation_strategy, weight_streaming_budget=self.weight_streaming_budget)) diff --git a/tools/Polygraphy/polygraphy/tools/args/comparator/data_loader.py b/tools/Polygraphy/polygraphy/tools/args/comparator/data_loader.py index d203e66f..635860cb 100644 --- a/tools/Polygraphy/polygraphy/tools/args/comparator/data_loader.py +++ b/tools/Polygraphy/polygraphy/tools/args/comparator/data_loader.py @@ -90,6 +90,15 @@ def add_parser_args_impl(self): dest="iterations", ) + self._array_modules = ["numpy", "torch"] + self.group.add_argument( + "--data-loader-backend-module", + type=str, + choices=self._array_modules, + help=f"The module to use for generating input arrays. Currently supported options: {', '.join(self._array_modules)}", + default=None, + ) + custom_loader_group = self.group.add_mutually_exclusive_group() custom_loader_group.add_argument( "--load-inputs", @@ -128,6 +137,7 @@ def parse_impl(self, args): load_inputs_paths (List[str]): Path(s) from which to load inputs. data_loader_script (str): Path to a custom script to load inputs. data_loader_func_name (str): Name of the function in the custom data loader script that loads data. + data_loader_backend_module (str): Module to be used that provides arrays. """ def omit_none_tuple(tup): @@ -168,6 +178,8 @@ def omit_none_tuple(tup): self.load_inputs_paths = args_util.get(args, "load_inputs_paths") + self.data_loader_backend_module = args_util.get(args, "data_loader_backend_module") + self.data_loader_script, self.data_loader_func_name = args_util.parse_script_and_func_name( args_util.get(args, "data_loader_script"), default_func_name="load_data" ) @@ -224,6 +236,7 @@ def _add_to_script_helper(self, script, user_input_metadata_str=None): int_range=self._int_range, float_range=self._float_range, val_range=self.val_range, + data_loader_backend_module=self.data_loader_backend_module, ) if data_loader: script.add_import(imports=["DataLoader"], frm="polygraphy.comparator") diff --git a/tools/Polygraphy/polygraphy/tools/check/subtool/lint.py b/tools/Polygraphy/polygraphy/tools/check/subtool/lint.py index 4c900851..0b45c500 100644 --- a/tools/Polygraphy/polygraphy/tools/check/subtool/lint.py +++ b/tools/Polygraphy/polygraphy/tools/check/subtool/lint.py @@ -16,30 +16,25 @@ # import contextlib +import enum import functools import io -import enum -import sys -import os import json -import tempfile +import os import re +import sys +import tempfile from collections import OrderedDict from typing import Optional, Union -from polygraphy.tools import util as tools_util from polygraphy import mod -from polygraphy.logger import G_LOGGER -from polygraphy.exception import PolygraphyException -from polygraphy.tools.args import ( - OnnxLoadArgs, - ModelArgs, - DataLoaderArgs, - OnnxrtSessionArgs, -) from polygraphy.comparator import IterationResult -from polygraphy.tools.base import Tool +from polygraphy.exception import PolygraphyException from polygraphy.json import save_json +from polygraphy.logger import G_LOGGER +from polygraphy.tools import util as tools_util +from polygraphy.tools.args import DataLoaderArgs, ModelArgs, OnnxLoadArgs, OnnxrtSessionArgs +from polygraphy.tools.base import Tool onnx = mod.lazy_import("onnx") gs = mod.lazy_import("onnx_graphsurgeon>=0.3.21") @@ -88,7 +83,7 @@ class Lint(Tool): 6. Large models (>2GB) require external data to be in same directory as the model file, custom paths to external data are not supported. """ - CUSTOM_OP_EXCEPTION_SUBSTR = "No opset import for domain" + CUSTOM_OP_EXCEPTION_SUBSTRS = ["No opset import for domain", "is not a registered function/op"] ONNX_CHECKER_IGNORE_SUBSTR = "Bad node spec for node" INVALID_ONNX_EXCEPTION_SUBSTR = "Error parsing message with type 'onnx.ModelProto'" MAXIMUM_PROTOBUF = 2e9 # 2GB @@ -809,11 +804,14 @@ def _report_unused_info(graph: "gs.Graph"): # NOTE: This is only done if early-exiting, as otherwise these warnings tend to be repeats # of node level warnings/exceptions. if warn_str: - self.report.add( - Lint.Level.WARNING, - Lint.Source.ONNXRUNTIME, - warn_str, - ) + warnings = warn_str.split('\n') + for warning in warnings: + if len(warning) > 0: + self.report.add( + Lint.Level.WARNING, + Lint.Source.ONNXRUNTIME, + warning, + ) ### report.add(unused nodes and tensors) ### _report_unused_info(graph) @@ -843,7 +841,7 @@ def _report_unused_info(graph: "gs.Graph"): inference_output, exception, _, _ = _ort_inference_check(model_bytes, lcm.feed_dict()) # NOTE: we ignore stdout and stderr as it contains info from polygraphy not relevant to linting. err_str = str(exception) if exception else "" - if Lint.CUSTOM_OP_EXCEPTION_SUBSTR in err_str: + if any([substr in err_str for substr in Lint.CUSTOM_OP_EXCEPTION_SUBSTRS]): self.report.add( level=Lint.Level.WARNING, source=Lint.Source.ONNXRUNTIME, diff --git a/tools/Polygraphy/polygraphy/tools/inspect/subtool/capability.py b/tools/Polygraphy/polygraphy/tools/inspect/subtool/capability.py index 34844ac2..7f4aa9de 100644 --- a/tools/Polygraphy/polygraphy/tools/inspect/subtool/capability.py +++ b/tools/Polygraphy/polygraphy/tools/inspect/subtool/capability.py @@ -16,6 +16,7 @@ # import os +from collections import OrderedDict from polygraphy import mod from polygraphy.common.interface import TypedDict from polygraphy.logger import G_LOGGER @@ -88,6 +89,30 @@ def supports_model(path): return supported, nodelists, parser +def parse(path): + """ + Invokes the ONNX parser's `parse` on the specified model. + + Args: + path (str): The path to the ONNX model. + + Returns: + Tuple[bool, parser]: + (1) Whether the model was parsed successfully. + (2) The TensorRT ONNX parser instance. + """ + _, network = trt_backend.create_network() + parser = trt.OnnxParser(network, trt_backend.get_trt_logger()) + + try: + parser.parse + except AttributeError: + trt_util.fail_unavailable("parse in tensorrt.OnnxParser") + + supported = parser.parse(common_backend.bytes_from_path(path), path) + return supported, parser + + def save_subgraph(onnx_save_args, graph, start, end, prefix="", use_tmp_file=False): """ Extracts a subgraph from the main graph and saves it to disk. @@ -147,9 +172,37 @@ def gen_results_summary(final_unsupported): return summary +def gen_results_summary_no_partitioning(stack_trace_to_errors): + """ + Generates a results summary given all the errors with a corresponding stack trace. + + Args: + stack_trace_to_errors (``OrderedDict[str, List[Tuple[str, str, str]]]``): + Reported errors with a corresponding stack trace. + + Returns: + str: A summary of all the unsupported ops in model, along with reasons and stack traces. + """ + stack_trace_width = max(map(len, list(stack_trace_to_errors.keys()) + ["Stack trace "])) + op_width = max(max(len(op) for errors_per_stack in stack_trace_to_errors.values() for op, _, _ in errors_per_stack), len("Operator ")) + node_width = max(max(len(node) for errors_per_stack in stack_trace_to_errors.values() for _, node, _ in errors_per_stack), len("Node ")) + reason_width = max(len(reason) for errors_per_stack in stack_trace_to_errors.values() for _, _, reason in errors_per_stack) + + summary = "===== Summary =====\n" + + header = f"{'Stack trace':{stack_trace_width}} | {'Operator':{op_width}} | {'Node':{node_width}} | {'Reason':{reason_width}}\n" + summary += header + "-" * len(header) + "\n" + + for stack_trace, op_node_reason in stack_trace_to_errors.items(): + for op, node, reason in op_node_reason: + summary += f"{stack_trace:{stack_trace_width}} | {op:{op_width}} | {node:{node_width}} | {reason:{reason_width}}\n" + return summary + + class Capability(Tool): """ - Determine the capability of TensorRT to run an ONNX graph. Graph will be paritioned into supported and unsupported subgraphs. + Determine the capability of TensorRT to run an ONNX graph. Graph will be either partitioned into supported and unsupported subgraphs + or only analyzed in terms of statically checked errors. """ def __init__(self): @@ -162,8 +215,52 @@ def get_subscriptions_impl(self): OnnxLoadArgs(outputs_opt_prefix=False), OnnxSaveArgs(output_default_path="polygraphy_capability_dumps", allow_multiple_models=True), ] + + def add_parser_args_impl(self, parser): + parser.add_argument( + "--with-partitioning", + help="Whether to partition the model graph on the nodes with parsing failures", + action="store_true", + ) def run_impl(self, args): + if args.with_partitioning: + self.supports_model_variant() + else: + self.no_partitioning_variant() + + def no_partitioning_variant(self): + supported, parser = parse(self.arg_groups[ModelArgs].path) + if supported: + G_LOGGER.info("Graph is fully supported by TensorRT; Will not report errors.") + return + + stack_trace_to_errors = OrderedDict() + for err_idx in range(parser.num_errors): + parser_error = parser.get_error(err_idx) + stack_trace = "" + if parser_error.local_function_stack_size() > 0: + for function_idx in range(parser_error.local_function_stack_size()): + stack_trace += parser_error.local_function_stack()[function_idx] + if function_idx != parser_error.local_function_stack_size() - 1: + stack_trace += " -> " + + if stack_trace not in stack_trace_to_errors: + stack_trace_to_errors[stack_trace] = [] + + node_operator = parser_error.node_operator() + node_name = parser_error.node_name() + parser_error_desc = str(parser_error) + stack_trace_to_errors[stack_trace].append(tuple((node_operator, node_name, parser_error_desc))) + + summary = gen_results_summary_no_partitioning(stack_trace_to_errors) + + G_LOGGER.info(summary) + util.save_file( + summary, os.path.join(self.arg_groups[OnnxSaveArgs].path, "results.txt"), "w", description="results" + ) + + def supports_model_variant(self): supported, nodelists, _ = supports_model(self.arg_groups[ModelArgs].path) if supported: G_LOGGER.info("Graph is fully supported by TensorRT; Will not generate subgraphs.") diff --git a/tools/Polygraphy/polygraphy/tools/plugin/README.md b/tools/Polygraphy/polygraphy/tools/plugin/README.md new file mode 100644 index 00000000..2cd0869a --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/plugin/README.md @@ -0,0 +1,35 @@ +# Plugin + +## Table of Contents + +- [Introduction](#introduction) +- [Subtools](#subtools) +- [Usage](#usage) +- [Examples](#examples) + + +## Introduction + +The `plugin` tool helps with plugin substitution in an onnx model. +The plugins need to advertise the graph pattern that they can substitute. +This is done by placing a file called pattern.py inside the plugin's directory. +See for example [toyPlugin](../../../examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin) + +## Subtools + +- `match` finds potential opportunities for substituting subgraphs with a plugin. + This creates an intermediate file (config.yaml) which the user should further edit to pick the desired plugin substitutions. + +- `list` finds potential opportunities for substituting subgraphs with a plugin, without generating an intermediate file. + This command is to list the potential substitutions, a kind of a dry run of the match tool. + +- `replace` replaces subgraphs with plugins, based on the intermediate file (config.yaml). + +## Usage + +See `polygraphy plugin -h` for usage information. + + +## Examples + +For examples, see [this directory](../../../examples/cli/plugin) diff --git a/tools/Polygraphy/polygraphy/tools/plugin/__init__.py b/tools/Polygraphy/polygraphy/tools/plugin/__init__.py new file mode 100644 index 00000000..f0bc1fd6 --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/plugin/__init__.py @@ -0,0 +1 @@ +from polygraphy.tools.plugin.plugin import Plugin diff --git a/tools/Polygraphy/polygraphy/tools/plugin/plugin.py b/tools/Polygraphy/polygraphy/tools/plugin/plugin.py new file mode 100644 index 00000000..41d1b3a6 --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/plugin/plugin.py @@ -0,0 +1,34 @@ +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from polygraphy.tools.base import Tool +from polygraphy.tools.plugin.subtool import Match, ListPlugins, Replace + + +class Plugin(Tool): + """ + Plugin related operations on an onnx model. + """ + + def __init__(self): + super().__init__("plugin") + + def get_subtools_impl(self): + return "Plugin Subtools", [ + Match(), + ListPlugins(), + Replace(), + ] diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/__init__.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/__init__.py new file mode 100644 index 00000000..5d07d5bb --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/__init__.py @@ -0,0 +1,4 @@ +from polygraphy.tools.plugin.subtool.match import Match +from polygraphy.tools.plugin.subtool.list_plugins import ListPlugins +from polygraphy.tools.plugin.subtool.replace import Replace +from polygraphy.tools.plugin.subtool.plugin_base import PluginBase diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/list_plugins.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/list_plugins.py new file mode 100644 index 00000000..2b068fb2 --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/list_plugins.py @@ -0,0 +1,36 @@ +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# + +""" +Analyzes onnx model for potential plugin substitutions. +""" + +from polygraphy.tools.plugin.subtool.plugin_base import PluginBase + +class ListPlugins(PluginBase): + """ + Analyze an onnx model for potential plugin substitutions. + """ + + def __init__(self): + super().__init__("list") + + def add_parser_args_impl(self, parser): + super().add_parser_args_impl(parser) + + def run_impl(self, args): + super().match_plugin(args=args, list_plugins=True) diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/match.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/match.py new file mode 100644 index 00000000..0c7b0a2a --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/match.py @@ -0,0 +1,43 @@ +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# + +""" +Analyzes onnx model for potential plugin substitutions. +""" + +from polygraphy.tools.plugin.subtool.plugin_base import PluginBase + + +class Match(PluginBase): + """ + Analyze an onnx model for potential plugin substitutions. + """ + + def __init__(self): + super().__init__("match") + + def add_parser_args_impl(self, parser): + super().add_parser_args_impl(parser) + parser.add_argument( + "-o", + "--output", + help="Full path where to save the intermediate file. Defaults to a file called config.yaml in the model directory.", + required=False, + ) + + def run_impl(self, args): + super().match_plugin(args=args, list_plugins=False) diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/plugin_base.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/plugin_base.py new file mode 100644 index 00000000..b594f5c3 --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/plugin_base.py @@ -0,0 +1,131 @@ +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# + +""" +Analyzes onnx model for potential plugin substitutions. +""" + +import glob +from polygraphy import mod +from polygraphy.logger import G_LOGGER +from polygraphy.tools import Tool +from polygraphy.tools.args import DataLoaderArgs, OnnxLoadArgs, ModelArgs, OnnxInferShapesArgs +import os + +# Your tool should lazily import any external dependencies. By doing so, +# we avoid creating hard dependencies on other packages. +# Additionally, this allows Polygraphy to automatically install required packages +# as they are needed, instead of requiring the user to do so up front. +common_backend = mod.lazy_import("polygraphy.backend.common") +gs = mod.lazy_import("onnx_graphsurgeon>=0.5.0") +np = mod.lazy_import("numpy") +onnx = mod.lazy_import("onnx") +yaml = mod.lazy_import("yaml", pkg_name="pyyaml") + +class PluginBase(Tool): + """ + Analyze an onnx model for potential plugin substitutions. + """ + GRAPH_PATTERN_FILE_NAME="pattern.py" + + def __init__(self, name=None): + super().__init__(name) + self.plugin_dir = None + + def get_subscriptions_impl(self): + return [ + ModelArgs(model_opt_required=True), + OnnxInferShapesArgs(), + OnnxLoadArgs(), + DataLoaderArgs(), + ] + + def add_parser_args_impl(self, parser): + parser.add_argument("--plugin-dir", help="Plugin directory.", required=True) + include_exclude = parser.add_mutually_exclusive_group() + include_exclude.add_argument("--include", help="Names of plugins to include. Format: `--include ...`", required=False, nargs="+", type=str, default=[]) + include_exclude.add_argument("--exclude", help="Names of plugins to exclude. Format: `--exclude ...`", required=False, nargs="+", type=str, default=[]) + + def run_impl(self, args): + raise NotImplementedError("run_impl() must be implemented by child classes") + + def match_plugin(self, args, list_plugins=False): + + self.plugin_dir = os.path.abspath(args.plugin_dir) + full_pattern = os.path.join(self.plugin_dir, "*", self.GRAPH_PATTERN_FILE_NAME) + + plugin_set = {os.path.basename(os.path.dirname(x)) for x in glob.glob(pathname=full_pattern, recursive=False)} + + if args.include: + plugin_set.intersection_update(set(args.include)) + + if args.exclude: + plugin_set.difference_update(set(args.exclude)) + + graph = gs.import_onnx(self.arg_groups[OnnxLoadArgs].load_onnx()) + + # list of plugin substitution instances (conent of config.yaml) + out_yaml = [] + plugin_frequency = dict.fromkeys(plugin_set, 0) + + # for each plugin, see if there is any match in the onnx model + for plugin in plugin_set: + G_LOGGER.info(f"checking {plugin} in model") + plugin_yaml = {} + + #build pattern from plugin + plugin_pattern_loc = os.path.join(self.plugin_dir, plugin, self.GRAPH_PATTERN_FILE_NAME) + graph_pattern = common_backend.invoke_from_script(plugin_pattern_loc, "get_plugin_pattern") + + matched_subgraphs = graph_pattern.match_all(graph) + if matched_subgraphs: + plugin_frequency[plugin] += len(matched_subgraphs) + + plugin_yaml["name"] = plugin + plugin_yaml["instances"] = [] + + for sg in matched_subgraphs: + def get_names(tensors): + return [tensor.name for tensor in tensors] + + inputs = get_names(sg.inputs) + outputs = get_names(sg.outputs) + attributes = common_backend.invoke_from_script(plugin_pattern_loc, "get_plugin_attributes", sg) + plugin_yaml["instances"].append({ + "inputs": inputs, + "outputs": outputs, + "attributes": attributes + }) + + out_yaml.append(plugin_yaml) + + if list_plugins: + G_LOGGER.info("the following plugins would be used:") + G_LOGGER.info(plugin_frequency) + return + + config_yaml = os.path.join(os.path.dirname(args.model_file),"config.yaml") + if args.output: + config_yaml = args.output + + with open(config_yaml, "w") as stream: + yaml.dump_all( + out_yaml, + stream, + default_flow_style=False, + sort_keys=False + ) diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/replace.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/replace.py new file mode 100755 index 00000000..73e95f14 --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/replace.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# + +""" +Replaces a subgraph in an onnx model with a plugin. +""" + +from polygraphy import mod +from polygraphy.logger import G_LOGGER +from polygraphy.tools import Tool +from polygraphy.tools.args import ( + DataLoaderArgs, + OnnxLoadArgs, + ModelArgs, + OnnxInferShapesArgs, +) +import os + +# Your tool should lazily import any external dependencies. By doing so, +# we avoid creating hard dependencies on other packages. +# Additionally, this allows Polygraphy to automatically install required packages +# as they are needed, instead of requiring the user to do so up front. + +gs = mod.lazy_import("onnx_graphsurgeon>=0.5.0") +onnx = mod.lazy_import("onnx") +yaml = mod.lazy_import("yaml", pkg_name="pyyaml") + + +def replace_with_plugin(graph, op, inputs, outputs, attrs=None): + """ + replaces a subgraph with a plugin + """ + + # Disconnect output nodes of all input tensors + for inp in inputs: + inp.outputs.clear() + + # Disconnet input nodes of all output tensors + for out in outputs: + out.inputs.clear() + + # Insert the new node. + new_node = graph.layer(op=op, inputs=inputs, outputs=outputs, attrs=attrs) + + # Remove the now-dangling subgraph. + graph.cleanup().toposort() + + return new_node + + +class Replace(Tool): + # Polygraphy will use the docstring of the tool child class to generate + # the summary for the command-line help output. + """ + Replace a subgraph in an onnx model with a plugin. + """ + + def __init__(self): + super().__init__("replace") + + def get_subscriptions_impl(self): + return [ + ModelArgs(model_opt_required=True), + OnnxInferShapesArgs(), + OnnxLoadArgs(), + DataLoaderArgs(), + ] + + def add_parser_args_impl(self, parser): + parser.add_argument("--plugin-dir", help="Plugin directory.", required=True) + parser.add_argument( + "-o", "--output", help="Where to save the modified model", required=True + ) + parser.add_argument("--config", help="location of config.yaml.") + + def run_impl(self, args): + graph = gs.import_onnx(self.arg_groups[OnnxLoadArgs].load_onnx()) + tmap = graph.tensors() + config_yaml = os.path.join(os.path.dirname(args.model_file), "config.yaml") + if args.config: + config_yaml = args.config + + with open(config_yaml, "r") as stream: + in_yaml = yaml.safe_load_all(stream) + + for plugin in in_yaml: + plugin_name = plugin["name"] + for instance in plugin["instances"]: + inputs = [tmap[tensor_name] for tensor_name in instance["inputs"]] + outputs = [tmap[tensor_name] for tensor_name in instance["outputs"]] + attrs = instance["attributes"] + + replace_with_plugin( + graph=graph, + op=plugin_name, + inputs=inputs, + outputs=outputs, + attrs=attrs, + ) + + onnx.save(gs.export_onnx(graph), args.output) diff --git a/tools/Polygraphy/polygraphy/tools/registry.py b/tools/Polygraphy/polygraphy/tools/registry.py index 9025edb6..ed4597d4 100644 --- a/tools/Polygraphy/polygraphy/tools/registry.py +++ b/tools/Polygraphy/polygraphy/tools/registry.py @@ -56,6 +56,7 @@ def try_register_tool(module, tool_class): try_register_tool("polygraphy.tools.template", "Template") try_register_tool("polygraphy.tools.debug", "Debug") try_register_tool("polygraphy.tools.data", "Data") +try_register_tool("polygraphy.tools.plugin", "Plugin") # Check that tool names are unique tool_names = [tool.name for tool in TOOL_REGISTRY] diff --git a/tools/Polygraphy/polygraphy/tools/sparse.py b/tools/Polygraphy/polygraphy/tools/sparse.py index 391e98ba..2cdbbfff 100644 --- a/tools/Polygraphy/polygraphy/tools/sparse.py +++ b/tools/Polygraphy/polygraphy/tools/sparse.py @@ -44,6 +44,8 @@ def __init__(self, model): self.tname2producer[t] = n self.prune_infos = dict() + self.sparse_tensors = set() + self.weights_skip = set() # Look back through Q/DQ/Cast nodes def __tensor(self, t, axis): @@ -150,6 +152,11 @@ def _walk_nodes(self): pinfo = prune_infos[i] G_LOGGER.super_verbose(f"Processing tensor {i + 1}/{count}: {pinfo}") t = self.w_name2obj[pinfo.name] + if t.name in self.weights_skip: + G_LOGGER.warning( + f"Skipping tensor: {t.name} since it was marked to skip pruning" + ) + continue supported_dtypes = [ onnx.TensorProto.FLOAT, onnx.TensorProto.FLOAT16, @@ -187,7 +194,9 @@ def process(self, check): pinfo = prune_infos[i] tensor = self.w_name2obj[pinfo.name] G_LOGGER.extra_verbose(f"Checking tensor {i + 1}/{count}: {pinfo.name}") - process_tensor(pinfo, tensor, True) + is_sparse = process_tensor(pinfo, tensor, True) + if is_sparse: + self.sparse_tensors.add(tensor.name) G_LOGGER.finish(f"Finished checking {count} tensors. ") return None else: @@ -203,7 +212,8 @@ def process(self, check): return build_new_model(self.model, new_w_name2obj) - def prune(self): + def prune(self, weights_skip=set()): + self.weights_skip = weights_skip return self.process(False) def check(self): @@ -244,7 +254,7 @@ def bf16_zeros_in_int32(v): zeros = bf16_zeros_in_int32(i32_data_0) + bf16_zeros_in_int32(i32_data_0) if zeros < 2: G_LOGGER.warning(f"Found non-sparse tensor: {tensor.name}") - return tensor + return False else: if is_raw_data: # data is 8bit array, bf16 is 16bit @@ -260,7 +270,7 @@ def bf16_zeros_in_int32(v): if check: G_LOGGER.info(f"Found sparse tensor: {tensor.name}") - return tensor + return True else: if is_raw_data: tensor.raw_data = bytes(data) @@ -309,7 +319,7 @@ def short2long(idx): if check: if min0_vabs != 0 or min1_vabs != 0: G_LOGGER.warning(f"Found non-sparse tensor: {tensor.name}") - return tensor + return False else: min0_idx = short2long(min0_idx) min1_idx = short2long(min1_idx) @@ -318,7 +328,7 @@ def short2long(idx): if check: G_LOGGER.info(f"Found sparse tensor: {tensor.name}") - return tensor + return True else: # pack raw data pack and then push to the model data = data.reshape(dims) diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/README.md b/tools/Polygraphy/polygraphy/tools/surgeon/README.md index b09041e6..ef37f3c2 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/README.md +++ b/tools/Polygraphy/polygraphy/tools/surgeon/README.md @@ -28,6 +28,9 @@ to modify an ONNX model. - [EXPERIMENTAL] `prune` can prune an ONNX model to have [2:4 structured sparsity](https://developer.nvidia.com/blog/accelerating-inference-with-sparsity-using-ampere-and-tensorrt/) by setting the some values of the weights to zero. Note that this will *not* retain the accuracy of the model and should hence be used only for functional testing. +- [EXPERIMENTAL] `weight-strip` can strip the initializers of selective nodes in an ONNX model. If an initializer is identified as 2:4 structured sparse, the sparsity structure is encoded in the initializer to preserve sparsity during reconstruction. The tool only supports 2:4 structured sparsity. + +- [EXPERIMENTAL] `weight-reconstruct` reads a weightless ONNX model and fills the empty initializers with proxy tensors. The reconstruction preserves 2:4 structed sparsity for initializers that were identified as sparse during weight stripping. ## Usage diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/__init__.py b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/__init__.py index 344be628..3381db74 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/__init__.py +++ b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/__init__.py @@ -2,3 +2,5 @@ from polygraphy.tools.surgeon.subtool.insert import Insert from polygraphy.tools.surgeon.subtool.sanitize import Sanitize from polygraphy.tools.surgeon.subtool.prune import Prune +from polygraphy.tools.surgeon.subtool.weight_strip import WeightStripper +from polygraphy.tools.surgeon.subtool.weight_reconstruct import WeightReconstructor diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/weight_reconstruct.py b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/weight_reconstruct.py new file mode 100644 index 00000000..0725a90e --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/weight_reconstruct.py @@ -0,0 +1,88 @@ +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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 ctypes +from polygraphy import mod +from polygraphy.logger import G_LOGGER +from polygraphy.tools.sparse import SparsityPruner +from polygraphy.tools.surgeon.subtool.base import BaseSurgeonSubtool +from polygraphy.tools.args import ModelArgs, OnnxLoadArgs, OnnxSaveArgs + +np = mod.lazy_import("numpy") +onnx = mod.lazy_import("onnx") +torch = mod.lazy_import("torch") + +class WeightReconstructor(BaseSurgeonSubtool): + """ + Reconstruct proxy weights in the Stripped ONNX model + """ + def __init__(self): + super().__init__("weight-reconstruct") + + def show_start_end_logging_impl(self, args): + return True + + def get_subscriptions_impl(self): + return [ + ModelArgs(model_opt_required=True, input_shapes_opt_name=False, required_model_type="onnx"), + OnnxLoadArgs(allow_shape_inference=False, outputs_opt_prefix=False, allow_from_tf=False), + OnnxSaveArgs(allow_shape_inference=False, output_opt_required=True), + ] + + def run_impl_surgeon(self, args): + def reconstruct_weights(model): + G_LOGGER.start(f"Beginning weight reconstruction...") + # Skip Sparsity Pruning of weights not marked as "SPARSE_2_4" + skip_weight_sparsify = set() + num_reconstructed = 0 + for initializer in model.graph.initializer: + doc_string = initializer.doc_string + + # If not marked as weightless, leave initializer untouched + if "TRT_WEIGHTLESS" not in doc_string: + skip_weight_sparsify.add(initializer.name) + continue + _, sparse_str = doc_string.split('/') + + # If not sparse, add to skip list + if not sparse_str: + skip_weight_sparsify.add(initializer.name) + + weight_dtype = onnx.helper.tensor_dtype_to_np_dtype(initializer.data_type) + weight_shape = tuple(initializer.dims) + proxy_weight_tensor = np.random.randn(*weight_shape).astype(weight_dtype) + proxy_weight_bytes = proxy_weight_tensor.data.tobytes() + if initializer.data_type == onnx.TensorProto.BFLOAT16: + proxy_weight_tensor = torch.from_numpy(proxy_weight_tensor).to(torch.bfloat16) + proxy_weight_bytes = bytes((ctypes.c_byte * proxy_weight_tensor.numel() + * proxy_weight_tensor.element_size()).from_address(proxy_weight_tensor.untyped_storage().data_ptr())) + assert weight_shape == proxy_weight_tensor.shape + assert initializer.raw_data == b"" + + G_LOGGER.verbose(f"Reconstructing weights for the {initializer.name} initializer") + num_reconstructed += 1 + initializer.raw_data = proxy_weight_bytes + + # Call Sparsity Pruner tool to convert selected weights to sparse weights + G_LOGGER.info("Calling Sparsity Pruner to prune selected weights") + sparsity_pruner = SparsityPruner(model) + model = sparsity_pruner.prune(weights_skip=skip_weight_sparsify) + G_LOGGER.finish(f"Finished reconstructing {num_reconstructed} weights") + return model + + model = super().load_model() + reconstructed_model = reconstruct_weights(model) + super().save_model(reconstructed_model) \ No newline at end of file diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/weight_strip.py b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/weight_strip.py new file mode 100644 index 00000000..a47f2c0a --- /dev/null +++ b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/weight_strip.py @@ -0,0 +1,419 @@ +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from polygraphy import mod +from polygraphy.logger import G_LOGGER +from polygraphy.tools.args.base import BaseArgs +from polygraphy.tools.args import util as args_util +from polygraphy.tools.sparse import SparsityPruner +from polygraphy.tools.surgeon.subtool.base import BaseSurgeonSubtool +from polygraphy.tools.args import ModelArgs, OnnxLoadArgs, OnnxSaveArgs + +gs = mod.lazy_import("onnx_graphsurgeon") +np = mod.lazy_import("numpy") +onnx = mod.lazy_import("onnx") + +class WeightStripperArgs(BaseArgs): + """ + Weight Stripping: weight stripping + """ + def add_parser_args_impl(self): + self.group.add_argument( + "--exclude-list", + help="Path to text file containing a list of initializers to skip", + default=None, + required=False + ) + def parse_impl(self, args): + """ + Parses command-line arguments and populates the following attributes: + + Attributes: + exclude_list (str): Path to text file containing a list of initializers to skip. + """ + self.exclude_list = args_util.get(args, "exclude_list") + + def get_exclude_list(self): + if not self.exclude_list: + return set() + with open(self.exclude_list) as fp: + lines = [line.rstrip() for line in fp] + return set(lines) + +def get_patterns(): + """ + Base Patterns contain single ops: Conv, ConvTranspose, Gemm, Gather, MatMul, Slice + + Q/DQ Patterns contain sequences with Q/DQ ops followed by a few base patterns: + [QuantizeLinear, DequantizeLinear, Conv] + [QuantizeLinear, DequantizeLinear, ConvTranspose] + [QuantizeLinear, DequantizeLinear, Gemm] + [QuantizeLinear, DequantizeLinear, MatMul] + + Transpose Patterns contain sequences with the Transpose op followed by a few base patterns: + [Transpose, Conv] + [Transpose, ConvTranspose] + [Transpose, Gemm] + [Transpose, MatMul] + """ + base_patterns = [] + + # dictionary storing the index of the input the Producer output can be linked to + input_positions = { + 'Conv': [0], + 'ConvTranspose': [0], + 'Gemm': [0, 1, 2], + 'MatMul': [0, 1], + } + + # Conv with Weight input + conv_w = gs.GraphPattern() + in_0 = conv_w.variable() + w = conv_w.variable() + conv_w_out = conv_w.add("conv_w", "Conv", inputs=[in_0, w]) + conv_w.set_output_tensors([conv_w_out]) + base_patterns.append(conv_w) + + # Conv with Weight and Bias inputs + conv_w_b = gs.GraphPattern() + in_0 = conv_w_b.variable() + w = conv_w_b.variable() + b = conv_w_b.variable() + conv_w_b_out = conv_w_b.add("conv_w_b", "Conv", inputs=[in_0, w, b]) + conv_w_b.set_output_tensors([conv_w_b_out]) + base_patterns.append(conv_w_b) + + # ConvTranspose with Weight input + convt_w = gs.GraphPattern() + in_0 = convt_w.variable() + w = convt_w.variable() + convt_w_out = convt_w.add("convt_w", "ConvTranspose", inputs=[in_0, w]) + convt_w.set_output_tensors([convt_w_out]) + base_patterns.append(convt_w) + + # ConvTranspose with Weight and Bias inputs + convt_w_b = gs.GraphPattern() + in_0 = convt_w_b.variable() + w = convt_w_b.variable() + b = convt_w_b.variable() + convt_w_b_out = convt_w_b.add("convt_w_b", "ConvTranspose", inputs=[in_0, w, b]) + convt_w_b.set_output_tensors([convt_w_b_out]) + base_patterns.append(convt_w_b) + + # Gemm with A and B inputs + gemm_1 = gs.GraphPattern() + in_0 = gemm_1.variable() + in_1 = gemm_1.variable() + gemm_1_out = gemm_1.add("gemm_1", "Gemm", inputs=[in_0, in_1]) + gemm_1.set_output_tensors([gemm_1_out]) + base_patterns.append(gemm_1) + + # Gemm with A, B and C inputs + gemm_2 = gs.GraphPattern() + in_0 = gemm_2.variable() + in_1 = gemm_2.variable() + in_2 = gemm_2.variable() + gemm_2_out = gemm_2.add("gemm_2", "Gemm", inputs=[in_0, in_1, in_2]) + gemm_2.set_output_tensors([gemm_2_out]) + base_patterns.append(gemm_2) + + # MatMul + matmul = gs.GraphPattern() + in_0 = matmul.variable() + in_1 = matmul.variable() + matmul_out = matmul.add("matmul", "MatMul", inputs=[in_0, in_1]) + matmul.set_output_tensors([matmul_out]) + base_patterns.append(matmul) + + # Q/DQ patterns + # QuantizeLinear with y_scale input + q_1 = gs.GraphPattern() + in_0 = q_1.variable() + y_scale = q_1.variable() + q_1_out = q_1.add("q_1", "QuantizeLinear", inputs=[in_0, y_scale]) + q_1.set_output_tensors([q_1_out]) + + # QuantizeLinear with y_scale and y_zero_point inputs + q_2 = gs.GraphPattern() + in_0 = q_2.variable() + y_scale = q_2.variable() + y_zero_point = q_2.variable() + q_2_out = q_2.add("q_2", "QuantizeLinear", inputs=[in_0, y_scale, y_zero_point]) + q_2.set_output_tensors([q_2_out]) + + # DequantizeLinear with x_scale input + dq_1 = gs.GraphPattern() + in_0 = dq_1.variable() + x_scale = dq_1.variable() + dq_1_out = dq_1.add("dq_1", "DequantizeLinear", inputs=[in_0, x_scale]) + dq_1.set_output_tensors([dq_1_out]) + + # QuantizeLinear with y_scale and y_zero_point inputs + dq_2 = gs.GraphPattern() + in_0 = dq_2.variable() + x_scale = dq_2.variable() + x_zero_point = dq_2.variable() + dq_2_out = dq_2.add("dq_2", "DequantizeLinear", inputs=[in_0, x_scale, x_zero_point]) + dq_2.set_output_tensors([dq_2_out]) + + qdq_patterns = [] + for op in base_patterns: + # all base patterns contain a single node + op_type = next(iter(op.nodes.values())).op + for input_pos in input_positions[op_type]: + for q in [q_1, q_2]: + for dq in [dq_1, dq_2]: + curr_pattern = gs.GraphPattern() + + q_inps = [curr_pattern.variable() for _ in range(len(q.input_tensors))] + q_out = curr_pattern.add("Q", q, inputs=q_inps) + + dq_inps = [curr_pattern.variable() for _ in range(len(dq.input_tensors) - 1)] + dq_out = curr_pattern.add("DQ", dq, inputs=[q_out] + dq_inps) + + # in case of Gemm with 2 inputs, skip the case where output of dq node is the 3rd input of Gemm + if len(op.input_tensors) <= input_pos: + continue + op_inps = [curr_pattern.variable() for _ in range(len(op.input_tensors))] + op_inps[input_pos] = dq_out + out = curr_pattern.add("base_op", op, inputs=op_inps) + curr_pattern.set_output_tensors([out]) + qdq_patterns.append(curr_pattern) + + # Transpose patterns + transpose_patterns = [] + transpose = gs.GraphPattern() + in_0 = transpose.variable() + transpose_out = transpose.add("transpose", "Transpose", inputs=[in_0]) + transpose.set_output_tensors([transpose_out]) + + for op in base_patterns: + # all base patterns contain a single node + op_type = next(iter(op.nodes.values())).op + for input_pos in input_positions[op_type]: + curr_pattern = gs.GraphPattern() + + t_inps = [curr_pattern.variable() for _ in range(len(transpose.input_tensors))] + t_out = curr_pattern.add("t", transpose, inputs=t_inps) + + # in case of Gemm with 2 inputs, skip the case where output of transpose node is the 3rd input of Gemm + if len(op.input_tensors) <= input_pos: + continue + op_inps = [curr_pattern.variable() for _ in range(len(op.input_tensors))] + op_inps[input_pos] = t_out + out = curr_pattern.add("base_op", op, inputs=op_inps) + curr_pattern.set_output_tensors([out]) + transpose_patterns.append(curr_pattern) + + # Gather + gather = gs.GraphPattern() + in_0 = gather.variable() + indices = gather.variable() + gather_out = gather.add("gather", "Gather", inputs=[in_0, indices]) + gather.set_output_tensors([gather_out]) + base_patterns.append(gather) + + # Slice without no optional inputs + slice_0 = gs.GraphPattern() + in_0 = slice_0.variable() + starts = slice_0.variable() + ends = slice_0.variable() + slice_0_out = slice_0.add("slice_0", "Slice", inputs=[in_0, starts, ends]) + slice_0.set_output_tensors([slice_0_out]) + base_patterns.append(slice_0) + + # Slice with axes inputs + slice_1 = gs.GraphPattern() + in_0 = slice_1.variable() + starts = slice_1.variable() + ends = slice_1.variable() + axes = slice_1.variable() + slice_1_out = slice_1.add("slice_1", "Slice", inputs=[in_0, starts, ends, axes]) + slice_1.set_output_tensors([slice_1_out]) + base_patterns.append(slice_1) + + # Slice with steps inputs + slice_2 = gs.GraphPattern() + in_0 = slice_2.variable() + starts = slice_2.variable() + ends = slice_2.variable() + steps = slice_2.variable() + slice_2_out = slice_2.add("slice_2", "Slice", inputs=[in_0, starts, ends, steps]) + slice_2.set_output_tensors([slice_2_out]) + base_patterns.append(slice_2) + + # Slice with axes and steps inputs + slice_3 = gs.GraphPattern() + in_0 = slice_3.variable() + starts = slice_3.variable() + ends = slice_3.variable() + axes = slice_3.variable() + steps = slice_3.variable() + slice_3_out = slice_3.add("slice_3", "Slice", inputs=[in_0, starts, ends, axes, steps]) + slice_3.set_output_tensors([slice_3_out]) + base_patterns.append(slice_3) + + return base_patterns + qdq_patterns + transpose_patterns + +def get_size_thresholds(): + """ + Strip the initializers of the ops only if the size threshold has been crossed + """ + return { + 'Conv': 1, + 'ConvTranspose': 1, + 'Gather': 1024, + 'Gemm': 1, + 'Plugin': 1024, + 'Slice': 1024, + } + +def get_inputs_to_strip(): + """ + Restrict the stripping of initializers of the ops to the input index specified + """ + return { + 'QuantizeLinear': set([0]), + 'Slice': set([0]), + } + +class WeightStripper(BaseSurgeonSubtool): + """ + Strip weights from the provided ONNX model + """ + def __init__(self): + super().__init__("weight-strip") + + def show_start_end_logging_impl(self, args): + return True + + def get_subscriptions_impl(self): + return [ + ModelArgs(model_opt_required=True, input_shapes_opt_name=False, required_model_type="onnx"), + OnnxLoadArgs(allow_shape_inference=False, outputs_opt_prefix=False, allow_from_tf=False), + OnnxSaveArgs(allow_shape_inference=False, output_opt_required=True), + WeightStripperArgs() + ] + + def __skip(self, node, inp, inp_index): + """ + Skip stripping the input based on pre-defined heuristics + + The function also modifies exclude_list if a matching input is found + """ + # restrict stripping of certain op inputs + if node.op in self.inputs_to_strip and inp_index not in self.inputs_to_strip[node.op]: + return True + # Skip inputs that are not initializers + if not isinstance(inp, gs.Constant): + return True + # Skip initializers with uint8 dtype + if inp.dtype == np.uint8: + return True + # Skip initializers specified in user defined skip list + if inp.name in self.exclude_list: + self.exclude_list.remove(inp.name) + return True + # Heuristic to strip based on size + if node.op in self.size_thresholds and inp.values.size < self.size_thresholds[node.op]: + return True + + return False + + def __get_matching_subgraph_inputs(self, graph): + """ + Use GraphPattern to find matching patterns in the graph + """ + for pattern in self.patterns: + subgraphs = pattern.match_all(graph) + for subgraph in subgraphs: + # the first node in the matched subgraph contains the initializer to strip + curr_node = next(iter(subgraph.values())) + while curr_node._get_node() is None: + curr_node = next(iter(curr_node.values())) + onnx_node = curr_node.onnx_node + for inp_index, inp in enumerate(onnx_node.inputs): + if not self.__skip(onnx_node, inp, inp_index): + self.initializers_to_strip.add(inp.name) + + def __get_plugin_inputs(self, nodes): + """ + Identify Plugin inputs to strip + """ + for node in nodes: + # If plugin found + if not onnx.defs.has(node.op): + for inp_index, inp in enumerate(node.inputs): + if not self.__skip(node, inp, inp_index): + G_LOGGER.verbose(f"Stripping initializer {inp.name} to the {node.op} op.") + self.initializers_to_strip.add(inp.name) + + def __get_sparse_tensors(self, model): + """ + Identify sparse tensors in the model + """ + sparsity_checker = SparsityPruner(model) + sparsity_checker.check() + sparse_tensors = sparsity_checker.sparse_tensors + return sparse_tensors + + def run_impl_surgeon(self, args): + def strip_weights(model): + G_LOGGER.start(f"Beginning weight stripping...") + G_LOGGER.warning(f"The model is expected to be constant folded to successfully capture all weights eligible for stripping") + graph = gs.import_onnx(model) + # check model sparsity + G_LOGGER.info("Querying Sparse Initializers in the model") + sparse_initializers = self.__get_sparse_tensors(model) + + # Call PatternMatcher to populate initializers_to_strip + self.__get_matching_subgraph_inputs(graph) + self.__get_plugin_inputs(graph.nodes) + + # Strip initializers identified by the PatternMatcher and Plugin Identifier + num_stripped = 0 + for initializer in model.graph.initializer: + if initializer.name in self.initializers_to_strip: + G_LOGGER.verbose(f"Stripping initializer {initializer.name}") + # Erase initializer data + initializer.raw_data = b"" + + # Check sparsity + sparse_str = "SPARSE_2_4" if initializer.name in sparse_initializers else "" + + # Update initializer doc_string + initializer.doc_string = '/'.join(["TRT_WEIGHTLESS", sparse_str]) + num_stripped += 1 + + if self.exclude_list: + G_LOGGER.warning(f"The following weights provided by the user to skip stripping were not found in the model: {self.exclude_list}.") + assert num_stripped == len(self.initializers_to_strip) + G_LOGGER.finish(f"Finished stripping {num_stripped} weights") + + return model + + # Initialize patterns + self.patterns = get_patterns() + self.size_thresholds = get_size_thresholds() + self.inputs_to_strip = get_inputs_to_strip() + self.initializers_to_strip = set() + + # load model + model = super().load_model() + + self.exclude_list = self.arg_groups[WeightStripperArgs].get_exclude_list() + stripped_model = strip_weights(model) + super().save_model(stripped_model) diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/surgeon.py b/tools/Polygraphy/polygraphy/tools/surgeon/surgeon.py index 5f5a0883..0acbc94c 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/surgeon.py +++ b/tools/Polygraphy/polygraphy/tools/surgeon/surgeon.py @@ -15,7 +15,7 @@ # limitations under the License. # from polygraphy.tools.base import Tool -from polygraphy.tools.surgeon.subtool import Extract, Insert, Sanitize, Prune +from polygraphy.tools.surgeon.subtool import Extract, Insert, Sanitize, Prune, WeightStripper, WeightReconstructor ################################# MAIN TOOL ################################# @@ -34,4 +34,6 @@ def get_subtools_impl(self): Sanitize(), Insert(), Prune(), + WeightStripper(), + WeightReconstructor(), ] diff --git a/tools/Polygraphy/polygraphy/util/array.py b/tools/Polygraphy/polygraphy/util/array.py index 1f3634ab..4381ccf6 100644 --- a/tools/Polygraphy/polygraphy/util/array.py +++ b/tools/Polygraphy/polygraphy/util/array.py @@ -44,7 +44,9 @@ def is_torch(obj): Returns: bool: Whether the object is a PyTorch tensor. """ - return mod.has_mod("torch") and isinstance(obj, torch.Tensor) + return ( + torch.is_installed() and torch.is_importable() and isinstance(obj, torch.Tensor) + ) @mod.export() @@ -59,7 +61,11 @@ def is_numpy(obj): Returns: bool: Whether the object is a NumPy array. """ - return mod.has_mod("numpy") and (isinstance(obj, np.ndarray) or isinstance(obj, np.generic)) + return ( + np.is_installed() + and np.is_importable() + and (isinstance(obj, np.ndarray) or isinstance(obj, np.generic)) + ) @mod.export() @@ -144,21 +150,26 @@ def dispatch(num_arrays=1): def dispatch_impl(func): def _get_key(obj): key = None - if is_torch(obj): - key = "torch" + + if is_device_view(obj): + key = "device_view" elif is_numpy(obj): key = "numpy" - elif is_device_view(obj): - key = "device_view" + elif is_torch(obj): + key = "torch" elif isinstance(obj, numbers.Number): key = "number" if not key: - G_LOGGER.critical(f"Function: {func.__name__} is unsupported for objects of type: {type(obj).__name__}") + G_LOGGER.critical( + f"Function: {func.__name__} is unsupported for objects of type: {type(obj).__name__}" + ) return key if num_arrays < 0: - G_LOGGER.critical(f"Function: {func.__name__} is unsupported with {num_arrays} < 0") + G_LOGGER.critical( + f"Function: {func.__name__} is unsupported with {num_arrays} < 0" + ) @functools.wraps(func) def wrapped(*args, **kwargs): @@ -188,7 +199,11 @@ def convert_array(obj): f"Function: {func.__name__} is unsupported for objects of type: {type(obj).__name__}" ) - converted_args = [obj0] + list(map(convert_array, args[1:num_arrays])) + list(args[num_arrays:]) + converted_args = ( + [obj0] + + list(map(convert_array, args[1:num_arrays])) + + list(args[num_arrays:]) + ) return mapping[key](*converted_args, **kwargs) @@ -331,7 +346,11 @@ def is_on_cpu(): Raises: PolygraphyException: if the input is of an unrecognized type. """ - return {"torch": lambda obj: obj.device.type == "cpu", "numpy": lambda _: True, "device_view": lambda _: False} + return { + "torch": lambda obj: obj.device.type == "cpu", + "numpy": lambda _: True, + "device_view": lambda _: False, + } @mod.export() @@ -349,7 +368,11 @@ def is_on_gpu(): Raises: PolygraphyException: if the input is of an unrecognized type. """ - return {"torch": lambda obj: obj.device.type == "cuda", "numpy": lambda _: False, "device_view": lambda _: True} + return { + "torch": lambda obj: obj.device.type == "cuda", + "numpy": lambda _: False, + "device_view": lambda _: True, + } @mod.export() @@ -413,7 +436,11 @@ def view(obj, dtype, shape): if is_device_view(obj): return obj.view(shape=shape, dtype=dtype) - dtype = DataType.to_dtype(dtype, "numpy") if is_numpy(obj) else DataType.to_dtype(dtype, "torch") + dtype = ( + DataType.to_dtype(dtype, "numpy") + if is_numpy(obj) + else DataType.to_dtype(dtype, "torch") + ) return obj.reshape(-1).view(dtype).reshape(shape) @@ -465,7 +492,11 @@ def impl_numpy(obj): return obj return np.ascontiguousarray(obj) - return {"torch": lambda obj: obj.contiguous(), "numpy": impl_numpy, "device_view": lambda obj: obj} + return { + "torch": lambda obj: obj.contiguous(), + "numpy": impl_numpy, + "device_view": lambda obj: obj, + } @mod.export() @@ -497,7 +528,9 @@ def numpy_impl(obj, shape): return { "numpy": numpy_impl, "torch": lambda obj, shape: obj.resize_(shape) if shape != obj.shape else obj, - "device_view": lambda obj, shape: obj.resize(shape) if shape != obj.shape else obj, + "device_view": lambda obj, shape: obj.resize(shape) + if shape != obj.shape + else obj, } @@ -669,8 +702,12 @@ def allclose(): DEFAULT_ATOL = 1e-8 return { - "torch": lambda lhs, rhs, rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL: torch.allclose(lhs, rhs, rtol=rtol, atol=atol), - "numpy": lambda lhs, rhs, rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL: np.allclose(lhs, rhs, rtol=rtol, atol=atol), + "torch": lambda lhs, rhs, rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL: torch.allclose( + lhs, rhs, rtol=rtol, atol=atol + ), + "numpy": lambda lhs, rhs, rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL: np.allclose( + lhs, rhs, rtol=rtol, atol=atol + ), } @@ -1019,10 +1056,16 @@ def torch_impl(obj, k, axis): if obj.dtype == torch.float16: if torch.cuda.is_available(): original_device = obj.device - ret = tuple(torch.topk(obj.to('cuda'), builtins.min(k, axis_len), dim=axis)) + ret = tuple( + torch.topk(obj.to("cuda"), builtins.min(k, axis_len), dim=axis) + ) return (ret[0].to(original_device), ret[1].to(original_device)) else: - ret = tuple(torch.topk(obj.type(torch.float32), builtins.min(k, axis_len), dim=axis)) + ret = tuple( + torch.topk( + obj.type(torch.float32), builtins.min(k, axis_len), dim=axis + ) + ) return (ret[0].type(torch.float16), ret[1].type(torch.float16)) return tuple(torch.topk(obj, builtins.min(k, axis_len), dim=axis)) diff --git a/tools/Polygraphy/tests/backend/trt/test_algorithm_selector.py b/tools/Polygraphy/tests/backend/trt/test_algorithm_selector.py index e9c65079..8e35eaa1 100644 --- a/tools/Polygraphy/tests/backend/trt/test_algorithm_selector.py +++ b/tools/Polygraphy/tests/backend/trt/test_algorithm_selector.py @@ -20,11 +20,19 @@ import pytest import tensorrt as trt from polygraphy import mod, util -from polygraphy.backend.trt import Algorithm, TacticRecorder, TacticReplayData, TacticReplayer, TensorInfo +from polygraphy.backend.trt import ( + Algorithm, + TacticRecorder, + TacticReplayData, + TacticReplayer, + TensorInfo, +) from polygraphy.exception import PolygraphyException -FakeAlgorithmContext = namedtuple("FakeAlgorithmContext", ["name", "num_inputs", "num_outputs"]) +FakeAlgorithmContext = namedtuple( + "FakeAlgorithmContext", ["name", "num_inputs", "num_outputs"] +) FakeAlgorithm = namedtuple("FakeAlgorithm", ["algorithm_variant", "io_info"]) FakeAlgorithm.get_algorithm_io_info = lambda this, index: this.io_info[index] @@ -36,25 +44,31 @@ def fake_context(name): def make_tensor_info( - tensor_format=trt.TensorFormat.LINEAR, dtype=trt.float32, strides=(1, 2, 3), vectorized_dim=-1, components_per_element=1, ): - return TensorInfo(tensor_format, dtype, strides, vectorized_dim, components_per_element) + return TensorInfo(dtype, strides, vectorized_dim, components_per_element) def fake_algo(implementation=6, tactic=0, io=None): io_info = [make_tensor_info()] * 2 if io: io_info = [] - for fmt, dtype, strides in io: + for dtype, strides in io: io_info.append( - TensorInfo(tensor_format=fmt, dtype=dtype, strides=strides, vectorized_dim=-1, components_per_element=1) + TensorInfo( + dtype=dtype, + strides=strides, + vectorized_dim=-1, + components_per_element=1, + ) ) - trt_algo = FakeAlgorithm(algorithm_variant=FakeAlgorithmVariant(implementation, tactic), io_info=io_info) + trt_algo = FakeAlgorithm( + algorithm_variant=FakeAlgorithmVariant(implementation, tactic), io_info=io_info + ) return trt_algo @@ -63,26 +77,20 @@ class TestTensorInfo: "left, right, expected", [ ( - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1), - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1), + TensorInfo(trt.float32, (1, 2, 3), -1, 1), + TensorInfo(trt.float32, (1, 2, 3), -1, 1), True, ), - # Different format - ( - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1), - TensorInfo(trt.TensorFormat.HWC, trt.float32, (1, 2, 3), -1, 1), - False, - ), # Different data type ( - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1), - TensorInfo(trt.TensorFormat.LINEAR, trt.float16, (1, 2, 3), -1, 1), + TensorInfo(trt.float32, (1, 2, 3), -1, 1), + TensorInfo(trt.float16, (1, 2, 3), -1, 1), False, ), # Different vectotrization ( - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1), - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), 0, 2), + TensorInfo(trt.float32, (1, 2, 3), -1, 1), + TensorInfo(trt.float32, (1, 2, 3), 0, 2), False, ), ], @@ -99,14 +107,14 @@ class TestAlgorithm: Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), True, ), # Same @@ -114,14 +122,14 @@ class TestAlgorithm: Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), Algorithm( 7, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), False, ), # Different implementation @@ -129,14 +137,14 @@ class TestAlgorithm: Algorithm( 6, 2, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), False, ), # Different tactic @@ -144,29 +152,14 @@ class TestAlgorithm: Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - ), - Algorithm( - 6, - 1, - inputs=[make_tensor_info(trt.TensorFormat.CHW32, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - ), - False, - ), # Different input format - ( - Algorithm( - 6, - 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.int8)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.int8)], + outputs=[make_tensor_info(trt.float32)], ), False, ), # Different input data type @@ -174,29 +167,14 @@ class TestAlgorithm: Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.CHW32, trt.float32)], - ), - False, - ), # Different output format - ( - Algorithm( - 6, - 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - ), - Algorithm( - 6, - 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.int8)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.int8)], ), False, ), # Different output data type @@ -204,14 +182,14 @@ class TestAlgorithm: Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)] * 2, - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)] * 2, + outputs=[make_tensor_info(trt.float32)], ), Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), False, ), # Different number of inputs @@ -219,14 +197,14 @@ class TestAlgorithm: Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)] * 2, + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)] * 2, ), Algorithm( 6, 1, - inputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], - outputs=[make_tensor_info(trt.TensorFormat.LINEAR, trt.float32)], + inputs=[make_tensor_info(trt.float32)], + outputs=[make_tensor_info(trt.float32)], ), False, ), # Different number of outputs @@ -276,21 +254,26 @@ class TestReplayer: def test_basic(self, replay): context, _, algo, replay_data, _ = replay replayer = TacticReplayer(replay_data) - selected = replayer.select_algorithms(context, [fake_algo(implementation=2), algo, fake_algo(tactic=1)]) + selected = replayer.select_algorithms( + context, [fake_algo(implementation=2), algo, fake_algo(tactic=1)] + ) assert selected == [1] def test_new_layer_falls_back(self, replay): _, _, _, replay_data, _ = replay replayer = TacticReplayer(replay_data) selected = replayer.select_algorithms( - fake_context(name="new_layer"), [fake_algo(2, 1), fake_algo(3, 4), fake_algo(5, 6)] + fake_context(name="new_layer"), + [fake_algo(2, 1), fake_algo(3, 4), fake_algo(5, 6)], ) assert selected == [0, 1, 2] def test_missing_algo_fails(self, replay): context, _, _, replay_data, _ = replay replayer = TacticReplayer(replay_data) - with pytest.raises(PolygraphyException, match="was not provided by TensorRT as a choice"): + with pytest.raises( + PolygraphyException, match="was not provided by TensorRT as a choice" + ): assert replayer.select_algorithms(context, [fake_algo(2, 1)]) == [0] @pytest.mark.parametrize( @@ -299,28 +282,46 @@ def test_missing_algo_fails(self, replay): fake_algo(2), fake_algo(tactic=2), fake_algo( - io=[(trt.TensorFormat.CHW32, trt.float32, (1, 2)), (trt.TensorFormat.LINEAR, trt.float32, (1, 2))] + io=[ + (trt.float32, (1, 2)), + (trt.float32, (1, 2)), + ] + ), + fake_algo( + io=[ + (trt.int8, (1, 2)), + (trt.float32, (1, 2)), + ] ), - fake_algo(io=[(trt.TensorFormat.LINEAR, trt.int8, (1, 2)), (trt.TensorFormat.LINEAR, trt.float32, (1, 2))]), fake_algo( - io=[(trt.TensorFormat.LINEAR, trt.float32, (1, 2)), (trt.TensorFormat.CHW32, trt.float32, (1, 2))] + io=[ + (trt.float32, (1, 2)), + (trt.float32, (1, 2)), + ] ), fake_algo( - io=[(trt.TensorFormat.LINEAR, trt.float32, (1, 2)), (trt.TensorFormat.LINEAR, trt.int32, (1, 2))] + io=[ + (trt.float32, (1, 2)), + (trt.int32, (1, 2)), + ] ), ], ) def test_different_algo_fails(self, replay, algo): context, _, _, replay_data, _ = replay replayer = TacticReplayer(replay_data) - with pytest.raises(PolygraphyException, match="was not provided by TensorRT as a choice"): + with pytest.raises( + PolygraphyException, match="was not provided by TensorRT as a choice" + ): assert replayer.select_algorithms(context, [algo]) == [0] def test_fails_if_wrong_selected(self, replay): context, _, _, replay_data, _ = replay replayer = TacticReplayer(replay_data) # We should be able to check tactics even if we're not recording them. - with pytest.raises(PolygraphyException, match="TensorRT selected a tactic different"): + with pytest.raises( + PolygraphyException, match="TensorRT selected a tactic different" + ): replayer.report_algorithms([context], [fake_algo(implementation=9)]) diff --git a/tools/Polygraphy/tests/backend/trt/test_config.py b/tools/Polygraphy/tests/backend/trt/test_config.py index 35624dba..381db3db 100644 --- a/tools/Polygraphy/tests/backend/trt/test_config.py +++ b/tools/Polygraphy/tests/backend/trt/test_config.py @@ -6,7 +6,13 @@ import tensorrt as trt from polygraphy import mod, util -from polygraphy.backend.trt import Calibrator, CreateConfig, Profile, network_from_onnx_bytes, postprocess_config +from polygraphy.backend.trt import ( + Calibrator, + CreateConfig, + Profile, + network_from_onnx_bytes, + postprocess_config, +) from polygraphy.common.struct import BoundedShape from polygraphy.comparator import DataLoader from polygraphy.datatype import DataType @@ -41,11 +47,16 @@ def test_defaults(self, identity_builder_network): assert not config.get_flag(trt.BuilderFlag.FP8) assert not config.get_flag(trt.BuilderFlag.VERSION_COMPATIBLE) assert not config.get_flag(trt.BuilderFlag.EXCLUDE_LEAN_RUNTIME) - assert config.hardware_compatibility_level == trt.HardwareCompatibilityLevel.NONE + assert ( + config.hardware_compatibility_level + == trt.HardwareCompatibilityLevel.NONE + ) assert config.num_optimization_profiles == 1 assert config.int8_calibrator is None with contextlib.suppress(AttributeError): - if mod.version(trt.__version__) >= mod.version("8.7"): + if mod.version(trt.__version__) >= mod.version("10.0"): + assert config.get_tactic_sources() == 24 + elif mod.version(trt.__version__) >= mod.version("8.7"): assert config.get_tactic_sources() == 29 elif mod.version(trt.__version__) >= mod.version("8.5"): assert config.get_tactic_sources() == 31 @@ -62,7 +73,11 @@ def test_defaults(self, identity_builder_network): @pytest.mark.parametrize( "engine_capability", - [trt.EngineCapability.STANDARD, trt.EngineCapability.SAFETY, trt.EngineCapability.DLA_STANDALONE], + [ + trt.EngineCapability.STANDARD, + trt.EngineCapability.SAFETY, + trt.EngineCapability.DLA_STANDALONE, + ], ) def test_engine_capability(self, identity_builder_network, engine_capability): builder, network = identity_builder_network @@ -84,15 +99,23 @@ def test_precision_constraints(self, identity_builder_network, flag): else: assert not obey_set and not prefer_set - @pytest.mark.skipif(mod.version(trt.__version__) < mod.version("8.6"), reason="Unsupported before TRT 8.6") + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.6"), + reason="Unsupported before TRT 8.6", + ) @pytest.mark.parametrize( "kwargs, expected_flag", [ ({"version_compatible": True}, "VERSION_COMPATIBLE"), - ({"version_compatible": True, "exclude_lean_runtime": True}, "EXCLUDE_LEAN_RUNTIME"), + ( + {"version_compatible": True, "exclude_lean_runtime": True}, + "EXCLUDE_LEAN_RUNTIME", + ), ], ) - def test_version_compatibility_flags(self, identity_builder_network, kwargs, expected_flag): + def test_version_compatibility_flags( + self, identity_builder_network, kwargs, expected_flag + ): builder, network = identity_builder_network loader = CreateConfig(**kwargs) with loader(builder, network) as config: @@ -136,11 +159,21 @@ def test_restricted(self, identity_builder_network, flag): ) + ( [ - ("disable_compilation_cache", trt.BuilderFlag.DISABLE_COMPILATION_CACHE), + ( + "disable_compilation_cache", + trt.BuilderFlag.DISABLE_COMPILATION_CACHE, + ), ] if mod.version(trt.__version__) >= mod.version("9.0") else [] ) + + ( + [ + ("strip_plan", trt.BuilderFlag.STRIP_PLAN), + ] + if mod.version(trt.__version__) >= mod.version("10.0") + else [] + ), ) @pytest.mark.parametrize("value", [True, False]) def test_flags(self, identity_builder_network, arg_name, flag_type, value): @@ -174,7 +207,14 @@ def test_use_dla(self, identity_builder_network): ([trt.TacticSource.CUBLAS, trt.TacticSource.CUBLAS_LT], 3), ([trt.TacticSource.CUBLAS, trt.TacticSource.CUDNN], 5), ([trt.TacticSource.CUBLAS_LT, trt.TacticSource.CUDNN], 6), - ([trt.TacticSource.CUDNN, trt.TacticSource.CUBLAS, trt.TacticSource.CUBLAS_LT], 7), + ( + [ + trt.TacticSource.CUDNN, + trt.TacticSource.CUBLAS, + trt.TacticSource.CUBLAS_LT, + ], + 7, + ), ( [ trt.TacticSource.CUDNN, @@ -196,7 +236,9 @@ def test_use_dla(self, identity_builder_network): ), ] - if mod.version(trt.__version__) >= mod.version("8.7"): + if mod.version(trt.__version__) >= mod.version("10.0"): + TACTIC_SOURCES_CASES[0] = (None, 24) + elif mod.version(trt.__version__) >= mod.version("8.7"): TACTIC_SOURCES_CASES[0] = (None, 29) @pytest.mark.parametrize("sources, expected", TACTIC_SOURCES_CASES) @@ -206,7 +248,10 @@ def test_tactic_sources(self, identity_builder_network, sources, expected): with loader(builder, network) as config: assert config.get_tactic_sources() == expected - @pytest.mark.skipif(mod.version(trt.__version__) < mod.version("8.7"), reason="API was added in TRT 8.7") + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.7"), + reason="API was added in TRT 8.7", + ) @pytest.mark.parametrize("flag", [True, False]) def test_error_on_timing_cache_miss(self, identity_builder_network, flag): builder, network = identity_builder_network @@ -300,12 +345,9 @@ def test_memory_pool_limits(self, pool_limits, identity_builder_network): @pytest.mark.parametrize( "preview_features", [ - [], - [trt.PreviewFeature.FASTER_DYNAMIC_SHAPES_0805], - [ - trt.PreviewFeature.FASTER_DYNAMIC_SHAPES_0805, - trt.PreviewFeature.DISABLE_EXTERNAL_TACTIC_SOURCES_FOR_CORE_0805, - ], + [trt.PreviewFeature.PROFILE_SHARING_0806] + if mod.version(trt.__version__) >= mod.version("10.0") + else [trt.PreviewFeature.FASTER_DYNAMIC_SHAPES_0805], ], ) def test_preview_features(self, identity_builder_network, preview_features): @@ -332,7 +374,8 @@ def test_quantization_flags(self, identity_builder_network, quantization_flags): assert config.get_quantization_flag(qf) == (qf in quantization_flags) @pytest.mark.skipif( - mod.version(trt.__version__) < mod.version("8.6"), reason="Unsupported for TRT versions prior to 8.6" + mod.version(trt.__version__) < mod.version("8.6"), + reason="Unsupported for TRT versions prior to 8.6", ) @pytest.mark.parametrize("level", range(6)) def test_builder_optimization_level(self, identity_builder_network, level): @@ -357,7 +400,8 @@ def test_hardware_compatibility_level(self, identity_builder_network, level): assert config.hardware_compatibility_level == level @pytest.mark.skipif( - mod.version(trt.__version__) < mod.version("8.6"), reason="Unsupported for TRT versions prior to 8.6" + mod.version(trt.__version__) < mod.version("8.6"), + reason="Unsupported for TRT versions prior to 8.6", ) @pytest.mark.parametrize("num_streams", range(3)) def test_max_aux_streams(self, identity_builder_network, num_streams): @@ -366,6 +410,30 @@ def test_max_aux_streams(self, identity_builder_network, num_streams): with loader(builder, network) as config: assert config.max_aux_streams == num_streams + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("9.0"), + reason="API was added in TRT 9.0", + ) + def test_progress_monitor(self, identity_builder_network): + class DummyProgressMonitor(trt.IProgressMonitor): + def __init__(self): + trt.IProgressMonitor.__init__(self) + + def phase_start(self, phase_name, parent_phase, num_steps): + pass + + def phase_finish(self, phase_name): + pass + + def step_complete(self, phase_name, step): + return True + + builder, network = identity_builder_network + progress_monitor = DummyProgressMonitor() + loader = CreateConfig(progress_monitor=progress_monitor) + with loader(builder, network) as config: + assert config.progress_monitor == progress_monitor + class TestPostprocessConfig: def test_with_config(self, identity_builder_network): diff --git a/tools/Polygraphy/tests/backend/trt/test_loader.py b/tools/Polygraphy/tests/backend/trt/test_loader.py index e3d2c8b2..652cc442 100644 --- a/tools/Polygraphy/tests/backend/trt/test_loader.py +++ b/tools/Polygraphy/tests/backend/trt/test_loader.py @@ -90,7 +90,9 @@ def identity_network(): @pytest.fixture(scope="session") def identity_identity_network(): - builder, network, parser = network_from_onnx_bytes(ONNX_MODELS["identity_identity"].loader) + builder, network, parser = network_from_onnx_bytes( + ONNX_MODELS["identity_identity"].loader + ) yield builder, network, parser @@ -123,7 +125,11 @@ def get_plugin_names(): return [pc.name for pc in trt.get_plugin_registry().plugin_creator_list] loader = LoadPlugins( - plugins=["nvinfer_plugin.dll" if sys.platform.startswith("win") else "libnvinfer_plugin.so"] + plugins=[ + "nvinfer_plugin.dll" + if sys.platform.startswith("win") + else "libnvinfer_plugin.so" + ] ) loader() assert get_plugin_names() @@ -152,7 +158,9 @@ def test_serialized_engine_loader_custom_runtime(self, identity_engine): assert isinstance(engine, trt.ICudaEngine) -@pytest.mark.skipif(mod.version(trt.__version__) < mod.version("8.6"), reason="API was added in TRT 8.6") +@pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.6"), reason="API was added in TRT 8.6" +) class TestLoadRuntime: def test_load_lean_runtime(self, nvinfer_lean_path): loader = LoadRuntime(nvinfer_lean_path) @@ -160,7 +168,9 @@ def test_load_lean_runtime(self, nvinfer_lean_path): assert isinstance(runtime, trt.Runtime) -@pytest.mark.skipif(mod.version(trt.__version__) < mod.version("8.6"), reason="API was added in TRT 8.6") +@pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.6"), reason="API was added in TRT 8.6" +) class TestSerializedVCEngineLoader: def test_serialized_vc_engine_loader_from_lambda(self, identity_vc_engine_bytes): with util.NamedTemporaryFile() as outpath: @@ -179,9 +189,10 @@ def test_serialized_engine_loader_from_buffer(self, identity_vc_engine_bytes): class TestNetworkFromOnnxBytes: def test_loader(self): - builder, network, parser = network_from_onnx_bytes(ONNX_MODELS["identity"].loader) + builder, network, parser = network_from_onnx_bytes( + ONNX_MODELS["identity"].loader + ) assert not network.has_implicit_batch_dimension - assert not network.has_explicit_precision @pytest.mark.parametrize( "kwargs, flag", @@ -190,7 +201,9 @@ def test_loader(self): else [], ) def test_network_flags(self, kwargs, flag): - builder, network, parser = network_from_onnx_bytes(ONNX_MODELS["identity"].loader, **kwargs) + builder, network, parser = network_from_onnx_bytes( + ONNX_MODELS["identity"].loader, **kwargs + ) assert network.get_flag(flag) @@ -198,7 +211,6 @@ class TestNetworkFromOnnxPath: def test_loader(self): builder, network, parser = network_from_onnx_path(ONNX_MODELS["identity"].path) assert not network.has_implicit_batch_dimension - assert not network.has_explicit_precision @pytest.mark.parametrize( "kwargs, flag", @@ -207,13 +219,17 @@ def test_loader(self): else [], ) def test_network_flags(self, kwargs, flag): - builder, network, parser = network_from_onnx_path(ONNX_MODELS["identity"].path, **kwargs) + builder, network, parser = network_from_onnx_path( + ONNX_MODELS["identity"].path, **kwargs + ) assert network.get_flag(flag) class TestModifyNetwork: def test_mark_layerwise(self, modifiable_network): - load_network = ModifyNetworkOutputs(modifiable_network, outputs=constants.MARK_ALL) + load_network = ModifyNetworkOutputs( + modifiable_network, outputs=constants.MARK_ALL + ) builder, network, parser = load_network() for layer in network: @@ -221,14 +237,18 @@ def test_mark_layerwise(self, modifiable_network): assert layer.get_output(index).is_network_output def test_mark_custom_outputs(self, modifiable_network): - builder, network, parser = modify_network_outputs(modifiable_network, outputs=["identity_out_0"]) + builder, network, parser = modify_network_outputs( + modifiable_network, outputs=["identity_out_0"] + ) assert network.num_outputs == 1 assert network.get_output(0).name == "identity_out_0" def test_exclude_outputs_with_mark_layerwise(self, modifiable_network): builder, network, parser = modify_network_outputs( - modifiable_network, outputs=constants.MARK_ALL, exclude_outputs=["identity_out_2"] + modifiable_network, + outputs=constants.MARK_ALL, + exclude_outputs=["identity_out_2"], ) assert network.num_outputs == 1 @@ -254,7 +274,9 @@ def test_unmark_shape_outputs(self, modifiable_reshape_network): def test_mark_outputs_layer_with_optional_inputs(self): builder, network = create_network() inp = network.add_input("input", shape=(1, 3, 224, 224), dtype=trt.float32) - slice_layer = network.add_slice(inp, (0, 0, 0, 0), (1, 3, 224, 224), (1, 1, 1, 1)) + slice_layer = network.add_slice( + inp, (0, 0, 0, 0), (1, 3, 224, 224), (1, 1, 1, 1) + ) # Set a tensor for `stride` to increment `num_inputs` so we have some inputs # which are `None` in between. @@ -323,7 +345,10 @@ class TestSetLayerPrecisions: def test_basic(self, modifiable_network): builder, network, parser = set_layer_precisions( modifiable_network, - layer_precisions={"onnx_graphsurgeon_node_1": trt.float16, "onnx_graphsurgeon_node_3": trt.int8}, + layer_precisions={ + "onnx_graphsurgeon_node_1": trt.float16, + "onnx_graphsurgeon_node_3": trt.int8, + }, ) assert network[0].precision == trt.float16 @@ -385,12 +410,19 @@ def test_can_build_without_parser_non_owning(self, identity_builder_network): def test_custom_runtime(self, identity_builder_network): builder, network = identity_builder_network - loader = EngineFromNetwork((builder, network), runtime=trt.Runtime(get_trt_logger())) + loader = EngineFromNetwork( + (builder, network), runtime=trt.Runtime(get_trt_logger()) + ) with loader() as engine: assert isinstance(engine, trt.ICudaEngine) - @pytest.mark.parametrize("use_config_loader, set_calib_profile", [(True, None), (False, False), (False, True)]) - def test_can_build_with_calibrator(self, identity_builder_network, use_config_loader, set_calib_profile): + @pytest.mark.parametrize( + "use_config_loader, set_calib_profile", + [(True, None), (False, False), (False, True)], + ) + def test_can_build_with_calibrator( + self, identity_builder_network, use_config_loader, set_calib_profile + ): builder, network = identity_builder_network calibrator = Calibrator(DataLoader()) @@ -412,7 +444,9 @@ def check_calibrator(): config.int8_calibrator = calibrator # Since this network has static shapes, we shouldn't need to set a calibration profile. if set_calib_profile: - calib_profile = Profile().fill_defaults(network).to_trt(builder, network) + calib_profile = ( + Profile().fill_defaults(network).to_trt(builder, network) + ) config.add_optimization_profile(calib_profile) config.set_calibration_profile(calib_profile) @@ -422,7 +456,9 @@ def check_calibrator(): check_calibrator() # Calibrator buffers should be freed after the build - assert all([buf.allocated_nbytes == 0 for buf in calibrator.device_buffers.values()]) + assert all( + [buf.allocated_nbytes == 0 for buf in calibrator.device_buffers.values()] + ) @pytest.mark.parametrize("path_mode", [True, False], ids=["path", "file-like"]) def test_timing_cache_generate_and_append(self, path_mode): @@ -459,7 +495,10 @@ def build_engine(model, cache): total_cache_size = get_file_size(total_cache.name) # The total cache should be larger than either of the individual caches. - assert total_cache_size >= const_foldable_cache_size and total_cache_size >= identity_cache_size + assert ( + total_cache_size >= const_foldable_cache_size + and total_cache_size >= identity_cache_size + ) # The total cache should also be smaller than or equal to the sum of the individual caches since # header information should not be duplicated. assert total_cache_size <= (const_foldable_cache_size + identity_cache_size) @@ -475,14 +514,27 @@ def test_serialize_engine(self, identity_network): class TestSaveEngine: def test_save_engine(self, identity_network): with util.NamedTemporaryFile() as outpath: - engine_loader = SaveEngine(EngineFromNetwork(identity_network), path=outpath.name) + engine_loader = SaveEngine( + EngineFromNetwork(identity_network), path=outpath.name + ) with engine_loader(): assert is_file_non_empty(outpath.name) class TestOnnxLikeFromNetwork: @pytest.mark.parametrize( - "model_name", ["identity", "empty_tensor_expand", "const_foldable", "and", "scan", "dim_param", "tensor_attr"] + "model_name", + [ + "identity", + "empty_tensor_expand", + "const_foldable", + "and", + "scan", + "dim_param", + "tensor_attr", + ], ) def test_onnx_like_from_network(self, model_name): - assert onnx_like_from_network(NetworkFromOnnxBytes(ONNX_MODELS[model_name].loader)) + assert onnx_like_from_network( + NetworkFromOnnxBytes(ONNX_MODELS[model_name].loader) + ) diff --git a/tools/Polygraphy/tests/backend/trt/test_runner.py b/tools/Polygraphy/tests/backend/trt/test_runner.py index d9e9c941..5e48b3e6 100644 --- a/tools/Polygraphy/tests/backend/trt/test_runner.py +++ b/tools/Polygraphy/tests/backend/trt/test_runner.py @@ -171,6 +171,36 @@ def test_multiple_profiles(self, use_optimization_profile): for shape in shapes: model.check_runner(runner, {"X": shape}) + + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("10.0"), + reason="Feature not present before 10.0", + ) + @pytest.mark.parametrize("allocation_strategy", [None, "static", "profile", "runtime"]) + def test_allocation_strategies(self, allocation_strategy): + model = ONNX_MODELS["residual_block"] + profile0_shapes = [(1, 3, 224, 224), (1, 3, 224, 224), (1, 3, 224, 224)] + profile1_shapes = [(1, 3, 224, 224), (1, 3, 224, 224), (2, 3, 224, 224)] + profile2_shapes = [(1, 3, 224, 224), (1, 3, 224, 224), (4, 3, 224, 224)] + network_loader = NetworkFromOnnxBytes(model.loader) + profiles = [ + Profile().add("gpu_0/data_0", *profile0_shapes), + Profile().add("gpu_0/data_0", *profile1_shapes), + Profile().add("gpu_0/data_0", *profile2_shapes), + ] + config_loader = CreateConfig(profiles=profiles) + engine = engine_from_network(network_loader, config_loader) + + for index, shapes in enumerate([profile0_shapes, profile1_shapes, profile2_shapes]): + with TrtRunner( + engine, + optimization_profile=index, + allocation_strategy=allocation_strategy, + ) as runner: + for shape in shapes: + model.check_runner(runner, {"gpu_0/data_0": shape}) + + def test_empty_tensor_with_dynamic_input_shape_tensor(self): model = ONNX_MODELS["empty_tensor_expand"] shapes = [(1, 2, 0, 3, 0), (2, 2, 0, 3, 0), (4, 2, 0, 3, 0)] @@ -304,3 +334,30 @@ def test_get_array_on_cpu(self, use_torch): assert isinstance(host_arr, torch.Tensor) else: assert isinstance(host_arr, np.ndarray) + + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("10.0"), + reason="Feature not present before 10.0", + ) + @pytest.mark.parametrize("budget", [None, -1]) + def test_weight_streaming(self, budget): + model = ONNX_MODELS["residual_block"] + profile0_shapes = [(1, 3, 224, 224), (1, 3, 224, 224), (1, 3, 224, 224)] + network_loader = NetworkFromOnnxBytes(model.loader) + profiles = [ + Profile().add("gpu_0/data_0", *profile0_shapes), + ] + network_loader = NetworkFromOnnxBytes(model.loader, strongly_typed=True) + config_loader = CreateConfig(profiles=profiles, weight_streaming=True) + engine = engine_from_network(network_loader, config_loader) + + if budget == np.inf: + # set to max size - 1 + budget = engine.streamable_weights_size - 1 + + with TrtRunner( + engine, + optimization_profile=0, + weight_streaming_budget=budget, + ) as runner: + model.check_runner(runner, {"gpu_0/data_0": profile0_shapes[0]}) diff --git a/tools/Polygraphy/tests/backend/trt/test_util.py b/tools/Polygraphy/tests/backend/trt/test_util.py index 8fb9772b..1bd9d44c 100644 --- a/tools/Polygraphy/tests/backend/trt/test_util.py +++ b/tools/Polygraphy/tests/backend/trt/test_util.py @@ -15,10 +15,12 @@ # limitations under the License. # +import contextlib from textwrap import dedent import pytest import tensorrt as trt + from polygraphy import mod from polygraphy.backend.trt import CreateConfig, Profile, create_network from polygraphy.backend.trt import util as trt_util @@ -39,7 +41,10 @@ def layer_class_mapping(): @pytest.mark.parametrize("layer_type", trt.LayerType.__members__.values()) def test_all_layer_types_mapped(layer_class_mapping, layer_type): - if layer_type == trt.LayerType.PLUGIN: + waived_layers = [trt.LayerType.PLUGIN] + with contextlib.suppress(AttributeError): + waived_layers.append(trt.LayerType.PLUGIN_V3) + if layer_type in waived_layers: pytest.skip("PLUGIN has no corresponding ILayer") assert layer_type in layer_class_mapping @@ -56,18 +61,37 @@ def adjust_memory_pool_limits_after_8_6(limits): def update_expected_output(expected): if mod.version(trt.__version__) >= mod.version("8.6"): - expected = expected.replace("MiB]", "MiB, TACTIC_DRAM: 1024.00 MiB]") + if mod.version(trt.__version__) >= mod.version("10.0"): + expected = expected.replace( + "MiB]", + "MiB, TACTIC_DRAM: 1024.00 MiB, TACTIC_SHARED_MEMORY: 1024.00 MiB]", + ) + else: + expected = expected.replace("MiB]", "MiB, TACTIC_DRAM: 1024.00 MiB]") if "Preview Features" not in expected: - expected = ( - dedent(expected).strip() - + "\nPreview Features | [FASTER_DYNAMIC_SHAPES_0805, DISABLE_EXTERNAL_TACTIC_SOURCES_FOR_CORE_0805]" - ) + if mod.version(trt.__version__) < mod.version("10.0"): + expected = ( + dedent(expected).strip() + + "\nPreview Features | [FASTER_DYNAMIC_SHAPES_0805, DISABLE_EXTERNAL_TACTIC_SOURCES_FOR_CORE_0805]" + ) + else: + expected = ( + dedent(expected).strip() + + "\nPreview Features | [PROFILE_SHARING_0806]" + ) if mod.version(trt.__version__) >= mod.version("8.7"): # CUBLAS_LT is not longer enabled by default expected = expected.replace("CUBLAS_LT, ", "") + if mod.version(trt.__version__) >= mod.version("10.0"): + expected = expected.replace( + "EngineCapability.DEFAULT", "EngineCapability.STANDARD" + ) + expected = expected.replace("CUBLAS, ", "") + expected = expected.replace("CUDNN, ", "") + return expected @@ -77,7 +101,9 @@ def update_expected_output(expected): [ ( CreateConfig( - memory_pool_limits=adjust_memory_pool_limits_after_8_6({trt.MemoryPoolType.WORKSPACE: 16 << 20}) + memory_pool_limits=adjust_memory_pool_limits_after_8_6( + {trt.MemoryPoolType.WORKSPACE: 16 << 20} + ) ), update_expected_output( """ @@ -91,7 +117,9 @@ def update_expected_output(expected): ), ( CreateConfig( - memory_pool_limits=adjust_memory_pool_limits_after_8_6({trt.MemoryPoolType.WORKSPACE: 16 << 20}), + memory_pool_limits=adjust_memory_pool_limits_after_8_6( + {trt.MemoryPoolType.WORKSPACE: 16 << 20} + ), tactic_sources=[], ), update_expected_output( @@ -106,7 +134,9 @@ def update_expected_output(expected): ), ( CreateConfig( - memory_pool_limits=adjust_memory_pool_limits_after_8_6({trt.MemoryPoolType.WORKSPACE: 4 << 20}) + memory_pool_limits=adjust_memory_pool_limits_after_8_6( + {trt.MemoryPoolType.WORKSPACE: 4 << 20} + ) ), update_expected_output( """ @@ -120,7 +150,9 @@ def update_expected_output(expected): ), ( CreateConfig( - memory_pool_limits=adjust_memory_pool_limits_after_8_6({trt.MemoryPoolType.WORKSPACE: 16 << 20}), + memory_pool_limits=adjust_memory_pool_limits_after_8_6( + {trt.MemoryPoolType.WORKSPACE: 16 << 20} + ), fp16=True, int8=True, tf32=True, @@ -139,8 +171,13 @@ def update_expected_output(expected): ), ( CreateConfig( - memory_pool_limits=adjust_memory_pool_limits_after_8_6({trt.MemoryPoolType.WORKSPACE: 16 << 20}), - profiles=[Profile().add("X", [1], [1], [1]), Profile().add("X", [2], [2], [2])], + memory_pool_limits=adjust_memory_pool_limits_after_8_6( + {trt.MemoryPoolType.WORKSPACE: 16 << 20} + ), + profiles=[ + Profile().add("X", [1], [1], [1]), + Profile().add("X", [2], [2], [2]), + ], ), update_expected_output( """ @@ -155,7 +192,9 @@ def update_expected_output(expected): ), ( CreateConfig( - memory_pool_limits=adjust_memory_pool_limits_after_8_6({trt.MemoryPoolType.WORKSPACE: 16 << 20}), + memory_pool_limits=adjust_memory_pool_limits_after_8_6( + {trt.MemoryPoolType.WORKSPACE: 16 << 20} + ), use_dla=True, ), update_expected_output( @@ -170,12 +209,34 @@ def update_expected_output(expected): ), ), ( - CreateConfig( - memory_pool_limits=adjust_memory_pool_limits_after_8_6({trt.MemoryPoolType.WORKSPACE: 16 << 20}), - preview_features=[trt.PreviewFeature.FASTER_DYNAMIC_SHAPES_0805], - ), - update_expected_output( + ( + CreateConfig( + memory_pool_limits=adjust_memory_pool_limits_after_8_6( + {trt.MemoryPoolType.WORKSPACE: 16 << 20} + ), + preview_features=[trt.PreviewFeature.PROFILE_SHARING_0806], + ), + update_expected_output( + """ + Flags | [] + Engine Capability | EngineCapability.DEFAULT + Memory Pools | [WORKSPACE: 16.00 MiB] + Tactic Sources | [CUBLAS, CUBLAS_LT, CUDNN, EDGE_MASK_CONVOLUTIONS, JIT_CONVOLUTIONS] + Profiling Verbosity | ProfilingVerbosity.DETAILED + Preview Features | [PROFILE_SHARING_0806] """ + ), + ) + if mod.version(trt.__version__) >= mod.version("10.0") + else ( + CreateConfig( + memory_pool_limits=adjust_memory_pool_limits_after_8_6( + {trt.MemoryPoolType.WORKSPACE: 16 << 20} + ), + preview_features=[trt.PreviewFeature.FASTER_DYNAMIC_SHAPES_0805], + ), + update_expected_output( + """ Flags | [] Engine Capability | EngineCapability.DEFAULT Memory Pools | [WORKSPACE: 16.00 MiB] @@ -183,21 +244,34 @@ def update_expected_output(expected): Profiling Verbosity | ProfilingVerbosity.DETAILED Preview Features | [FASTER_DYNAMIC_SHAPES_0805] """ - ), + ), + ) ), ], - ids=["default", "tactic-sources", "memory-pool-limits", "builder-flags", "profiles", "dla", "preview-features"], + ids=[ + "default", + "tactic-sources", + "memory-pool-limits", + "builder-flags", + "profiles", + "dla", + "preview-features", + ], ) def test_str_from_config(create_config, expected, dummy_network): config = create_config(*dummy_network) - assert trt_util.str_from_config(config) == dedent(expected).strip() + actual = trt_util.str_from_config(config) + expected = dedent(expected).strip() + assert actual == expected def test_get_all_tensors_layer_with_null_inputs(): builder, network = create_network() with builder, network: inp = network.add_input("input", shape=(1, 3, 224, 224), dtype=trt.float32) - slice_layer = network.add_slice(inp, (0, 0, 0, 0), (1, 3, 224, 224), (1, 1, 1, 1)) + slice_layer = network.add_slice( + inp, (0, 0, 0, 0), (1, 3, 224, 224), (1, 1, 1, 1) + ) # Set a tensor for `stride` to increment `num_inputs` so we have some inputs # which are `None` in between. diff --git a/tools/Polygraphy/tests/common/test_datatype.py b/tools/Polygraphy/tests/common/test_datatype.py index b60307f5..c9e5108a 100644 --- a/tools/Polygraphy/tests/common/test_datatype.py +++ b/tools/Polygraphy/tests/common/test_datatype.py @@ -44,8 +44,9 @@ def test_numpy(self, dtype): DataType.FLOAT8E4M3FNUZ, DataType.FLOAT8E5M2, DataType.FLOAT8E5M2FNUZ, + DataType.INT4, ]: - pytest.xfail("BFLOAT16 and FLOAT8 not supported by NumPy") + pytest.xfail("Type not supported by NumPy") np_type = dtype.numpy() assert DataType.to_dtype(dtype, "numpy") == np_type @@ -58,6 +59,11 @@ def test_numpy(self, dtype): @pytest.mark.parametrize("dtype", DATATYPES, ids=str) def test_onnxrt(self, dtype): + if dtype in [ + DataType.INT4, + ]: + pytest.skip("Type not supported by ONNX-RT") + onnxrt_type = DataType.to_dtype(dtype, "onnxruntime") assert dtype.onnxruntime() == onnxrt_type @@ -76,6 +82,11 @@ def test_onnxrt(self, dtype): @pytest.mark.parametrize("dtype", DATATYPES, ids=str) def test_onnx(self, dtype): + if dtype in [ + DataType.INT4, + ]: + pytest.skip("Type not supported by ONNX") + onnx_type = dtype.onnx() assert DataType.to_dtype(dtype, "onnx") == onnx_type @@ -93,7 +104,10 @@ def test_onnx(self, dtype): assert DataType.from_dtype(onnx_type) == dtype - @pytest.mark.skipif(mod.version(trt.__version__) < mod.version("8.7"), reason="Unsupported before TRT 8.7") + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.7"), + reason="Unsupported before TRT 8.7", + ) @pytest.mark.parametrize("dtype", DATATYPES, ids=str) def test_tensorrt(self, dtype): if dtype in [ @@ -147,8 +161,9 @@ def test_torch(self, dtype): DataType.UINT32, DataType.UINT64, DataType.STRING, + DataType.INT4, ]: - pytest.xfail("FLOAT8 not supported by ONNX-RT") + pytest.xfail("Type not supported by Torch") torch_type = dtype.torch() assert DataType.to_dtype(dtype, "torch") == torch_type diff --git a/tools/Polygraphy/tests/comparator/test_compare.py b/tools/Polygraphy/tests/comparator/test_compare.py index cfa6071c..97f255c4 100644 --- a/tools/Polygraphy/tests/comparator/test_compare.py +++ b/tools/Polygraphy/tests/comparator/test_compare.py @@ -25,7 +25,7 @@ build_torch = lambda a, **kwargs: util.array.to_torch(np.array(a, **kwargs)) -@pytest.mark.parametrize("array_type", [np.array, build_torch]) +@pytest.mark.parametrize("array_type", [np.array, build_torch], ids=["numpy", "torch"]) class TestSimpleCompareFunc: @pytest.mark.parametrize( "values0, values1, dtype, expected_max_absdiff, expected_max_reldiff", @@ -38,18 +38,36 @@ class TestSimpleCompareFunc: ([0], [1], DataType.UINT32, 1, 1.0), ([1], [0], DataType.UINT32, 1, np.inf), ([25], [30], DataType.INT8, 5, 5.0 / 30.0), - ([25], [30], DataType.FLOAT16, 5, np.array([5.0], dtype=np.float32) / np.array([30.0], dtype=np.float32)), + ( + [25], + [30], + DataType.FLOAT16, + 5, + np.array([5.0], dtype=np.float32) / np.array([30.0], dtype=np.float32), + ), ([1], [0], DataType.FLOAT16, 1, 1 / np.finfo(float).eps), ], ) - def test_comparison(self, values0, values1, dtype, expected_max_absdiff, expected_max_reldiff, array_type): + def test_comparison( + self, + values0, + values1, + dtype, + expected_max_absdiff, + expected_max_reldiff, + array_type, + ): if array_type != np.array: try: DataType.to_dtype(dtype, "torch") except: pytest.skip(f"Cannot convert {dtype} to torch") - iter_result0 = IterationResult(outputs={"output": array_type(values0, dtype=dtype.numpy())}) - iter_result1 = IterationResult(outputs={"output": array_type(values1, dtype=dtype.numpy())}) + iter_result0 = IterationResult( + outputs={"output": array_type(values0, dtype=dtype.numpy())} + ) + iter_result1 = IterationResult( + outputs={"output": array_type(values1, dtype=dtype.numpy())} + ) compare_func = CompareFunc.simple() acc = compare_func(iter_result0, iter_result1) @@ -67,36 +85,77 @@ def test_comparison(self, values0, values1, dtype, expected_max_absdiff, expecte "values0, values1, dtype, quantile, expected_abs_quantile, expected_rel_quantile", [ ([0, 0.1, 0.5, 0.75, 1], [2, 2, 2, 2, 2], DataType.FLOAT16, 0.5, 1.5, 0.75), - ([0, 0.1, 0.5, 0.75, 1], [2, 2, 2, 2, 2], DataType.FLOAT16, 0.75, 1.9, 0.95), - ([0.2, 0.2, 0.125, 0.11], [0.1, 0.1, 0.1, 0.1], DataType.FLOAT16, 0.5, 0.0625, 0.625), + ( + [0, 0.1, 0.5, 0.75, 1], + [2, 2, 2, 2, 2], + DataType.FLOAT16, + 0.75, + 1.9, + 0.95, + ), + ( + [0.2, 0.2, 0.125, 0.11], + [0.1, 0.1, 0.1, 0.1], + DataType.FLOAT16, + 0.5, + 0.0625, + 0.625, + ), ([0, 1, 2, 3, 4], [2, 2, 2, 2, 2], DataType.UINT8, 0.5, 1, 0.5), ([0, 1, 2, 3, 4], [2, 2, 2, 2, 2], DataType.UINT8, 0.75, 2, 1), - ] + ], ) - def test_qunatile(self, values0, values1, dtype, quantile, expected_abs_quantile, expected_rel_quantile, array_type): + def test_quantile( + self, + values0, + values1, + dtype, + quantile, + expected_abs_quantile, + expected_rel_quantile, + array_type, + ): if array_type != np.array: try: DataType.to_dtype(dtype, "torch") except: pytest.skip(f"Cannot convert {dtype} to torch") - iter_result0 = IterationResult(outputs={"output": array_type(values0, dtype=dtype.numpy())}) - iter_result1 = IterationResult(outputs={"output": array_type(values1, dtype=dtype.numpy())}) + iter_result0 = IterationResult( + outputs={"output": array_type(values0, dtype=dtype.numpy())} + ) + iter_result1 = IterationResult( + outputs={"output": array_type(values1, dtype=dtype.numpy())} + ) - compare_func = CompareFunc.simple(check_error_stat="quantile", error_quantile=quantile) + compare_func = CompareFunc.simple( + check_error_stat="quantile", error_quantile=quantile + ) acc = compare_func(iter_result0, iter_result1) comp_result = acc["output"] - assert np.isclose(comp_result.quantile_absdiff, expected_abs_quantile, atol=1e-4, rtol=1e-4) + assert np.isclose( + comp_result.quantile_absdiff, expected_abs_quantile, atol=1e-4, rtol=1e-4 + ) assert comp_result.quantile_absdiff <= comp_result.max_absdiff - assert (quantile>=0.5) == (comp_result.quantile_absdiff >= comp_result.median_absdiff) + assert (quantile >= 0.5) == ( + comp_result.quantile_absdiff >= comp_result.median_absdiff + ) - assert np.isclose(comp_result.quantile_reldiff, expected_rel_quantile, atol=1e-4, rtol=1e-4) + assert np.isclose( + comp_result.quantile_reldiff, expected_rel_quantile, atol=1e-4, rtol=1e-4 + ) assert comp_result.quantile_reldiff <= comp_result.max_reldiff - assert (quantile>=0.5) == (comp_result.quantile_reldiff >= comp_result.median_reldiff) + assert (quantile >= 0.5) == ( + comp_result.quantile_reldiff >= comp_result.median_reldiff + ) def test_can_compare_bool(self, array_type): - iter_result0 = IterationResult(outputs={"output": array_type(np.zeros((4, 4), dtype=bool))}) - iter_result1 = IterationResult(outputs={"output": array_type(np.ones((4, 4), dtype=bool))}) + iter_result0 = IterationResult( + outputs={"output": array_type(np.zeros((4, 4), dtype=bool))} + ) + iter_result1 = IterationResult( + outputs={"output": array_type(np.ones((4, 4), dtype=bool))} + ) compare_func = CompareFunc.simple() acc = compare_func(iter_result0, iter_result1) @@ -109,8 +168,12 @@ def test_per_output_tol(self, mode, array_type): OUT1_NAME = "output1" OUT_VALS = array_type(np.ones((4, 4))) - iter_result0 = IterationResult(outputs={OUT0_NAME: OUT_VALS, OUT1_NAME: OUT_VALS}) - iter_result1 = IterationResult(outputs={OUT0_NAME: OUT_VALS, OUT1_NAME: OUT_VALS + 1}) + iter_result0 = IterationResult( + outputs={OUT0_NAME: OUT_VALS, OUT1_NAME: OUT_VALS} + ) + iter_result1 = IterationResult( + outputs={OUT0_NAME: OUT_VALS, OUT1_NAME: OUT_VALS + 1} + ) # With default tolerances, out1 is wrong for the second result. compare_func = CompareFunc.simple() @@ -139,8 +202,12 @@ def test_per_output_tol_fallback(self, mode, array_type): OUT1_NAME = "output1" OUT_VALS = array_type(np.ones((4, 4))) - iter_result0 = IterationResult(outputs={OUT0_NAME: OUT_VALS + 1, OUT1_NAME: OUT_VALS}) - iter_result1 = IterationResult(outputs={OUT0_NAME: OUT_VALS, OUT1_NAME: OUT_VALS + 1}) + iter_result0 = IterationResult( + outputs={OUT0_NAME: OUT_VALS + 1, OUT1_NAME: OUT_VALS} + ) + iter_result1 = IterationResult( + outputs={OUT0_NAME: OUT_VALS, OUT1_NAME: OUT_VALS + 1} + ) acc = CompareFunc.simple()(iter_result0, iter_result1) assert not bool(acc[OUT0_NAME]) @@ -191,8 +258,12 @@ def test_default_tol_in_map(self, mode, array_type): ], ) def test_non_matching_outputs(self, shape, array_type): - iter_result0 = IterationResult(outputs={"output": array_type(np.zeros(shape, dtype=np.float32))}) - iter_result1 = IterationResult(outputs={"output": array_type(np.ones(shape, dtype=np.float32))}) + iter_result0 = IterationResult( + outputs={"output": array_type(np.zeros(shape, dtype=np.float32))} + ) + iter_result1 = IterationResult( + outputs={"output": array_type(np.ones(shape, dtype=np.float32))} + ) compare_func = CompareFunc.simple() @@ -210,8 +281,12 @@ def test_non_matching_outputs(self, shape, array_type): ], ) def test_check_error_stat(self, func, check_error_stat, array_type): - iter_result0 = IterationResult(outputs={"output": array_type(func((100,), dtype=np.float32))}) - iter_result1 = IterationResult(outputs={"output": array_type(func((100,), dtype=np.float32))}) + iter_result0 = IterationResult( + outputs={"output": array_type(func((100,), dtype=np.float32))} + ) + iter_result1 = IterationResult( + outputs={"output": array_type(func((100,), dtype=np.float32))} + ) iter_result0["output"][0] += 100 @@ -228,18 +303,30 @@ def test_check_error_stat(self, func, check_error_stat, array_type): def test_atol_rtol_either_pass(self, check_error_stat, array_type): # If either rtol/atol is sufficient, the compare_func should pass res0 = IterationResult(outputs={"output": array_type([1, 2], dtype=np.float32)}) - res1 = IterationResult(outputs={"output": array_type((1.25, 2.5), dtype=np.float32)}) + res1 = IterationResult( + outputs={"output": array_type((1.25, 2.5), dtype=np.float32)} + ) - assert not CompareFunc.simple(check_error_stat=check_error_stat)(res0, res1)["output"] + assert not CompareFunc.simple(check_error_stat=check_error_stat)(res0, res1)[ + "output" + ] - assert CompareFunc.simple(check_error_stat=check_error_stat, rtol=0.25)(res0, res1)["output"] - assert CompareFunc.simple(check_error_stat=check_error_stat, atol=0.5)(res0, res1)["output"] + assert CompareFunc.simple(check_error_stat=check_error_stat, rtol=0.25)( + res0, res1 + )["output"] + assert CompareFunc.simple(check_error_stat=check_error_stat, atol=0.5)( + res0, res1 + )["output"] def test_atol_rtol_combined_pass(self, array_type): # We should also be able to mix them - i.e. rtol might enough for some, atol for others. # If they cover the entire output range, it should pass. - res0 = IterationResult(outputs={"output": array_type([0, 1, 2, 3], dtype=np.float32)}) - res1 = IterationResult(outputs={"output": array_type((0.15, 1.25, 2.5, 3.75), dtype=np.float32)}) + res0 = IterationResult( + outputs={"output": array_type([0, 1, 2, 3], dtype=np.float32)} + ) + res1 = IterationResult( + outputs={"output": array_type((0.15, 1.25, 2.5, 3.75), dtype=np.float32)} + ) assert not CompareFunc.simple()(res0, res1)["output"] @@ -275,12 +362,20 @@ def test_per_output_error_stat(self, check_error_stat, array_type): atol = 0.4125 assert not CompareFunc.simple(atol=atol)(res0, res1)["output0"] - assert CompareFunc.simple(check_error_stat=check_error_stat, atol=atol)(res0, res1)["output0"] - assert CompareFunc.simple(check_error_stat=check_error_stat, atol=atol)(res0, res1)["output1"] + assert CompareFunc.simple(check_error_stat=check_error_stat, atol=atol)( + res0, res1 + )["output0"] + assert CompareFunc.simple(check_error_stat=check_error_stat, atol=atol)( + res0, res1 + )["output1"] def test_invalid_error_stat(self, array_type): - res0 = IterationResult(outputs={"output": array_type([0, 1, 2, 3], dtype=np.float32)}) - res1 = IterationResult(outputs={"output": array_type([0.15, 1.25, 2.5, 3.75], dtype=np.float32)}) + res0 = IterationResult( + outputs={"output": array_type([0, 1, 2, 3], dtype=np.float32)} + ) + res1 = IterationResult( + outputs={"output": array_type([0.15, 1.25, 2.5, 3.75], dtype=np.float32)} + ) with pytest.raises(PolygraphyException, match="Invalid choice"): CompareFunc.simple(check_error_stat="invalid-stat")(res0, res1) @@ -291,7 +386,9 @@ def test_nans_always_fail(self, check_error_stat, val0, val1, array_type): res0 = IterationResult(outputs={"output": array_type([val0], dtype=np.float32)}) res1 = IterationResult(outputs={"output": array_type([val1], dtype=np.float32)}) - assert not CompareFunc.simple(check_error_stat=check_error_stat)(res0, res1)["output"] + assert not CompareFunc.simple(check_error_stat=check_error_stat)(res0, res1)[ + "output" + ] @pytest.mark.parametrize("infinities_compare_equal", (False, True)) @pytest.mark.parametrize("val", (np.inf, -np.inf)) @@ -329,4 +426,7 @@ def test_index_tolerance(self, out0, out1, index_tolerance, expected, array_type res0 = IterationResult(outputs={"output": array_type(out0, dtype=np.int32)}) res1 = IterationResult(outputs={"output": array_type(out1, dtype=np.int32)}) - assert CompareFunc.indices(index_tolerance=index_tolerance)(res0, res1)["output"] == expected + assert ( + CompareFunc.indices(index_tolerance=index_tolerance)(res0, res1)["output"] + == expected + ) diff --git a/tools/Polygraphy/tests/comparator/test_data_loader.py b/tools/Polygraphy/tests/comparator/test_data_loader.py index 1bdb7d16..f60fea40 100644 --- a/tools/Polygraphy/tests/comparator/test_data_loader.py +++ b/tools/Polygraphy/tests/comparator/test_data_loader.py @@ -17,6 +17,7 @@ from collections import OrderedDict import numpy as np +import torch import pytest from polygraphy import constants, util @@ -27,8 +28,13 @@ from tests.models.meta import ONNX_MODELS from polygraphy.exception import PolygraphyException + def meta(dtype): - return TensorMetadata().add("X", dtype=dtype, shape=(4, 4)).add("Y", dtype=dtype, shape=(5, 5)) + return ( + TensorMetadata() + .add("X", dtype=dtype, shape=(4, 4)) + .add("Y", dtype=dtype, shape=(5, 5)) + ) class TestDataLoader: @@ -77,7 +83,9 @@ def test_can_use_min_max_shape(self, min_shape, max_shape, expected): @pytest.mark.parametrize("dtype", [np.int32, bool, np.float32, np.int64]) @pytest.mark.parametrize("range_val", [0, 1]) def test_range_min_max_equal(self, dtype, range_val): - data_loader = DataLoader(input_metadata=meta(dtype), val_range=(range_val, range_val)) + data_loader = DataLoader( + input_metadata=meta(dtype), val_range=(range_val, range_val) + ) feed_dict = data_loader[0] assert np.all(feed_dict["X"] == range_val) assert np.all(feed_dict["Y"] == range_val) @@ -96,7 +104,9 @@ def test_range_min_max_equal(self, dtype, range_val): ) def test_val_ranges(self, range): min_val, max_val, dtype = range - data_loader = DataLoader(input_metadata=meta(dtype), val_range=(min_val, max_val)) + data_loader = DataLoader( + input_metadata=meta(dtype), val_range=(min_val, max_val) + ) feed_dict = data_loader[0] assert np.all((feed_dict["X"] >= min_val) & (feed_dict["X"] <= max_val)) @@ -144,7 +154,9 @@ def test_no_shape_tensor_false_positive_negative_dims(self): data_loader.input_metadata = input_meta feed_dict = data_loader[0] - assert feed_dict["X"].shape == (3,) # Shape IS (3, ), because this is NOT a shape tensor + assert feed_dict["X"].shape == ( + 3, + ) # Shape IS (3, ), because this is NOT a shape tensor assert np.any( feed_dict["X"] != INPUT_DATA ) # Contents are not INPUT_DATA, since it's not treated as a shape value @@ -170,20 +182,39 @@ def test_non_user_provided_inputs_never_shape_tensors(self): feed_dict = data_loader[0] assert feed_dict["X"].shape == (3,) # Treat as a normal tensor - def test_generate_scalar(self): - data_loader = DataLoader(input_metadata=TensorMetadata().add("input", dtype=np.float32, shape=[])) - - assert data_loader[0]["input"].shape == tuple() + @pytest.mark.parametrize("dtype", [np.float32, np.int32]) + @pytest.mark.parametrize("data_loader_backend_module", ["torch", "numpy"]) + def test_generate_scalar(self, dtype, data_loader_backend_module): + data_loader = DataLoader( + input_metadata=TensorMetadata().add("input", dtype=dtype, shape=[]), + data_loader_backend_module=data_loader_backend_module, + ) + scalar = data_loader[0]["input"] + assert isinstance( + scalar, + np.ndarray if data_loader_backend_module == "numpy" else torch.Tensor, + ) + assert scalar.shape == tuple() def test_error_on_unsupported_numpy_type(self): input_meta = TensorMetadata().add("X", dtype=DataType.BFLOAT16, shape=(3,)) data_loader = DataLoader() data_loader.input_metadata = input_meta - with pytest.raises(PolygraphyException, match="Please use a custom data loader to provide inputs."): + with pytest.raises( + PolygraphyException, + match="Please use a custom data loader to provide inputs.", + ): data_loader[0] + def test_bf16_supported_torch(self): + input_meta = TensorMetadata().add("X", dtype=DataType.BFLOAT16, shape=(3,)) + data_loader = DataLoader(data_loader_backend_module="torch") + data_loader.input_metadata = input_meta + + assert util.array.is_torch(data_loader[0]["X"]) + build_torch = lambda a, **kwargs: util.array.to_torch(np.array(a, **kwargs)) @@ -217,7 +248,11 @@ def test_will_not_give_up_on_first_cache_miss(self, array_type): DATA[0]["Y"] = array_type(np.zeros(SHAPE, dtype=np.int64)) cache = DataLoaderCache(DATA) - cache.set_input_metadata(TensorMetadata().add("X", DataType.INT64, shape=SHAPE).add("Y", DataType.INT64, SHAPE)) + cache.set_input_metadata( + TensorMetadata() + .add("X", DataType.INT64, shape=SHAPE) + .add("Y", DataType.INT64, SHAPE) + ) # Populate the cache with bad X but good Y. # The data loader cache should fail to coerce X to the right shape and then reload it from the data loader. diff --git a/tools/Polygraphy/tests/helper.py b/tools/Polygraphy/tests/helper.py index 34b59362..e4141c72 100644 --- a/tools/Polygraphy/tests/helper.py +++ b/tools/Polygraphy/tests/helper.py @@ -28,7 +28,7 @@ "convert": [], "inspect": ["data", "model", "tactics", "capability", "diff-tactics"], "check": ["lint"], - "surgeon": ["extract", "insert", "sanitize", "prune"], + "surgeon": ["extract", "insert", "sanitize", "prune", "weight-strip", "weight-reconstruct"], "template": ["trt-network", "trt-config", "onnx-gs"], "debug": ["build", "precision", "reduce", "repeat"], "data": ["to-input"], diff --git a/tools/Polygraphy/tests/mod/test_dependencies.py b/tools/Polygraphy/tests/mod/test_dependencies.py index 069f848e..859bd1f9 100644 --- a/tools/Polygraphy/tests/mod/test_dependencies.py +++ b/tools/Polygraphy/tests/mod/test_dependencies.py @@ -257,6 +257,7 @@ def test_all_lazy_imports(self): "tensorrt", "tf2onnx", "torch", + "yaml", ] if sys.version_info < (3, 8): expected.append("importlib_metadata") diff --git a/tools/Polygraphy/tests/mod/test_importer.py b/tools/Polygraphy/tests/mod/test_importer.py index f76f2899..dc269b8c 100644 --- a/tools/Polygraphy/tests/mod/test_importer.py +++ b/tools/Polygraphy/tests/mod/test_importer.py @@ -79,7 +79,9 @@ def example(): assert example is not None example() - with pytest.raises(PolygraphyException, match="Could not import symbol: non_existent from"): + with pytest.raises( + PolygraphyException, match="Could not import symbol: non_existent from" + ): mod.import_from_script(f.name, "non_existent") assert sys.path == orig_sys_path @@ -102,9 +104,38 @@ def example(): def test_version_ok(self, ver, pref, expected): assert _version_ok(ver, pref) == expected - def test_has_mod(self, poly_venv): + def test_is_installed_works_when_package_name_differs_from_module_name( + self, poly_venv + ): + assert "onnxruntime" not in poly_venv.installed_packages() + assert "onnxruntime-gpu" not in poly_venv.installed_packages() + + poly_venv.run( + [poly_venv.python, "-m", "pip", "install", "onnxruntime-gpu", "--no-deps"] + ) + + # The `onnxruntime-gpu` package provides the `onnxruntime` module. + # `is_installed()` should be able to understand that. + poly_venv.run( + [ + poly_venv.python, + "-c", + "from polygraphy import mod; onnxrt = mod.lazy_import('onnxruntime<0'); assert onnxrt.is_installed()", + ] + ) + + @pytest.mark.parametrize( + "mod_check", ["mod.has_mod('colored')", "colored.is_installed()"] + ) + def test_has_mod(self, poly_venv, mod_check): assert "colored" not in poly_venv.installed_packages() - poly_venv.run([poly_venv.python, "-c", "from polygraphy import mod; assert not mod.has_mod('colored')"]) + poly_venv.run( + [ + poly_venv.python, + "-c", + f"from polygraphy import mod; colored = mod.lazy_import('colored'); assert not {mod_check}", + ] + ) poly_venv.run([poly_venv.python, "-m", "pip", "install", "colored==1.4.0"]) # Make sure `has_mod` doesn't actually import the package. diff --git a/tools/Polygraphy/tests/models/conv.onnx b/tools/Polygraphy/tests/models/conv.onnx index 85655b3bf29dfb633f151485f1b66626a1b67e52..9bc59e6529c61290cd9393ed61ca26a1fd9ee092 100644 GIT binary patch literal 4259 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 cC-F=FWx4-kRvB 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 VVZwO$f6jvv_rJLtmnX)T{~tG|vW@@% diff --git a/tools/Polygraphy/tests/models/graph_with_subgraph_matching_toy_plugin.onnx b/tools/Polygraphy/tests/models/graph_with_subgraph_matching_toy_plugin.onnx new file mode 100644 index 0000000000000000000000000000000000000000..9dde8cb7b49312e36402cbcb945182c5c50049eb GIT binary patch literal 240 zcmdJ957XX=Pro`wfB%Ys_R}r6H zlvt2aTw0W#nx6;ZL~(%~!@|YH!I%VO=Nk$k*hUa`7Dx?L9Lz=*H%3xp0%1F`a4`sQ F0|3!5CK><$ literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/tests/models/make_models.py b/tools/Polygraphy/tests/models/make_models.py index 30448366..cf3a1e3a 100644 --- a/tools/Polygraphy/tests/models/make_models.py +++ b/tools/Polygraphy/tests/models/make_models.py @@ -20,10 +20,13 @@ """ import os +import tempfile import numpy as np import onnx +import subprocess import onnx_graphsurgeon as gs - +from meta import ONNX_MODELS +from polygraphy.tools.sparse import SparsityPruner CURDIR = os.path.dirname(__file__) @@ -107,12 +110,41 @@ def split(self, inp, split, axis=0): attrs={"axis": axis, "split": split}, ) +@gs.Graph.register() +def transpose(self, inp, **kwargs): + return self.layer( + op="Transpose", + inputs=[inp], + outputs=["transpose_out"], + **kwargs + )[0] + +@gs.Graph.register() +def quantize_linear(self, inp, y_scale, y_zero_point, **kwargs): + return self.layer( + op="QuantizeLinear", + inputs=[inp, y_scale, y_zero_point], + outputs=["quantize_linear_out"], + **kwargs + )[0] + +@gs.Graph.register() +def dequantize_linear(self, inp, x_scale, x_zero_point, **kwargs): + return self.layer( + op="DequantizeLinear", + inputs=[inp, x_scale, x_zero_point], + outputs=["dequantize_linear_out"], + **kwargs + )[0] def save(graph, model_name): path = os.path.join(CURDIR, model_name) print(f"Writing: {path}") onnx.save(gs.export_onnx(graph), path) +def make_sparse(graph): + sparsity_pruner = SparsityPruner(gs.export_onnx(graph)) + return gs.import_onnx(sparsity_pruner.prune()) # Generates a model with multiple inputs/outputs: # @@ -396,7 +428,7 @@ def make_unbounded_dds(): make_unbounded_dds() -def make_small_matmul(name, dtype): +def make_small_matmul(name, dtype, save_sparse=False): M = 8 N = 8 K = 16 @@ -409,9 +441,10 @@ def make_small_matmul(name, dtype): g.outputs = [c] save(g, name) + if save_sparse: + save(make_sparse(g), 'sparse.'+name) - -make_small_matmul("matmul.onnx", np.float32) +make_small_matmul("matmul.onnx", np.float32, save_sparse=True) make_small_matmul("matmul.fp16.onnx", np.float16) @@ -431,6 +464,7 @@ def make_small_conv(name): g.outputs = [c] save(g, name) + save(make_sparse(g), 'sparse.'+name) make_small_conv("conv.onnx") @@ -724,4 +758,101 @@ def make_bad_graph_with_duplicate_node_names(): graph.outputs = [out] save(graph, "bad_graph_with_duplicate_node_names.onnx") -make_bad_graph_with_duplicate_node_names() \ No newline at end of file +make_bad_graph_with_duplicate_node_names() + +# Generates a model where the graph has a subgraph matching toyPlugin's graph pattern +def make_graph_with_subgraph_matching_toy_plugin(): + i0 = gs.Variable(name="i0", dtype=np.float32) + i1 = gs.Variable(name="i1", dtype=np.float32) + i2 = gs.Variable(name="i2", dtype=np.float32) + i3 = gs.Variable(name="i3", dtype=np.float32) + i4 = gs.Variable(name="i4", dtype=np.float32) + + o1 = gs.Variable(name="o1", dtype=np.float32) + o2 = gs.Variable(name="o2", dtype=np.float32) + + O_node = gs.Node(op="O", inputs=[i0], outputs=[i1], name="n1") + A_node = gs.Node(op="A", inputs=[i1], outputs=[i2], name="n2") + B_node = gs.Node(op="B", inputs=[i1], outputs=[i3], name="n3") + C_node = gs.Node(op="C", inputs=[i2,i3], outputs=[i4], attrs={"x":1}, name="n4") + D_node = gs.Node(op="D", inputs=[i4], outputs=[o1], name="n5") + E_node = gs.Node(op="E", inputs=[i4], outputs=[o2], name="n6") + + graph = gs.Graph(nodes=[O_node, A_node, B_node, C_node, D_node, E_node], inputs=[i0], outputs=[o1,o2]) + + save(graph, "graph_with_subgraph_matching_toy_plugin.onnx") +make_graph_with_subgraph_matching_toy_plugin() + +# Generates the following Graph +# +# The input to the Transpose op is an initializer +# +# Transpose +# | +# MatMul +# | +# out +# +def make_transpose_matmul(): + M = 8 + N = 8 + K = 16 + a = gs.Variable("a", shape=(M, K), dtype=np.float32) + g = gs.Graph(inputs=[a], opset=13) + val = np.random.uniform(-3, 3, size=K * N).astype(np.float32).reshape((N, K)) + b = gs.Constant("b", values=val) + b_transpose = g.transpose(b, name="transpose") + c = g.matmul(a, b_transpose, name="matmul") + c.dtype = np.float32 + g.outputs = [c] + + save(g, "transpose_matmul.onnx") + +make_transpose_matmul() + +# Generates the following Graph +# +# The input to the QuantizeLinear op is an initializer +# +# QuantizeLinear +# | +# DequantizeLinear +# | +# Conv +# | +# out +# +def make_qdq_conv(): + x = np.random.uniform(-3, 3, size=3*3*130).astype(np.float32).reshape((1, 3, 3, 130)) + y_scale = np.array([2, 4, 5], dtype=np.float32) + y_zero_point = np.array([84, 24, 196], dtype=np.uint8) + x_const = gs.Constant("x", values=x) + y_scale_const = gs.Constant("y_scale", values=y_scale) + y_zero_point_const = gs.Constant("y_zero_point", values=y_zero_point) + + weight = gs.Constant("Weights_0", values=np.ones((3, 3, 3, 3), dtype=np.float32)) + + g = gs.Graph(inputs=[], opset=13) + q_layer = g.quantize_linear(x_const, y_scale_const, y_zero_point_const) + dq_layer = g.dequantize_linear(q_layer, y_scale_const, y_zero_point_const) + out = g.conv(dq_layer, weight, [3, 3], name="Conv_0") + out.dtype = np.float32 + g.outputs = [out] + + save(g, "qdq_conv.onnx") + +make_qdq_conv() + +def make_weightless_network(model_name): + ipath = ONNX_MODELS[model_name].path + opath = os.path.join(CURDIR, "weightless." + model_name + ".onnx") + cmd = [f"polygraphy surgeon weight-strip {ipath} -o {opath}"] + subprocess.run(cmd, shell=True) + +make_weightless_network("matmul.fp16") +make_weightless_network("matmul.bf16") +make_weightless_network("sparse.matmul") +make_weightless_network("conv") +make_weightless_network("sparse.conv") +make_weightless_network("transpose_matmul") +make_weightless_network("qdq_conv") diff --git a/tools/Polygraphy/tests/models/matmul.exclude_list.txt b/tools/Polygraphy/tests/models/matmul.exclude_list.txt new file mode 100644 index 00000000..61780798 --- /dev/null +++ b/tools/Polygraphy/tests/models/matmul.exclude_list.txt @@ -0,0 +1 @@ +b diff --git a/tools/Polygraphy/tests/models/matmul.fp16.onnx b/tools/Polygraphy/tests/models/matmul.fp16.onnx index fd05f275261d1fe1197ca1886a999adce4d05661..a1cf59478b814516d9c28609b3da161ceff567da 100644 GIT binary patch delta 269 zcmV+o0rLKc1Be5VSAXm~2&zE9xwa=d>p#muwXn><3%`v(q`Dk30m0e3e849#D8D?t zio9CD#=D_CZ$Zzz13(uz+`mP*M?p`uRWK2{VnF~ti#7g1=Q_kbxHp-=cECBo(L4aX z`83GDP&`Sp2sFq$zQ8QJowbm^7CdFWqrsa&#JLJMgFP=fgnzaKw>CbrKOw=+K%PJb zt@Xh3!H__5LH902JoB)qzv)2Vy#qljvbnn(zHYcYK9#{^vrItqw06JrJxeb_HhsSp zJ)glDKk~pEK~lcHKybk#yR^IbybnOFKD@n%K;*!8C_O#MK!!ZrJ$AuhrYpNXz*)c~ Tw_L$hI~KuRHUcg!lUD&T09k*} delta 269 zcmV+o0rLKc1Be5VSAWsDFTwvoQ8`C2FFvikd_PV(8$rT4M!``*^*;E&qrYWA_B#;0 zS}(^n5VaS);K53`z`4FKJ;2>P-8r;A-#!*W)xh>SRlTx6Qo#s7{I^EEX~7jiGrdnh zx40L+k-_IXxxj_M20+n4);=deySeH(V=W}UWiO9GTfrl|On<9LKa;(rKjJ)zE#*L! zwemqJLBGMrJ9e>2L0!PFH7~m@z1%>eyxqL?yJ9&C!Jj%IKD@Snz=%BlKutVQz0$yy zIRm$Yz~MXAE}6VIuAac*xRI_?G_1h@zH&L8wB?Fu T!6H6WIJCh~IRbRLlUD&Tkkx{z diff --git a/tools/Polygraphy/tests/models/matmul.onnx b/tools/Polygraphy/tests/models/matmul.onnx index 1f9167ab4c56402a9723f8bc6f91dd724beffe31..99d82cff8764d5d6e7d71bffdf49720fdc46e175 100644 GIT binary patch delta 526 zcmV+p0`dKb1&9TZSAVVeq`$lj1VH3D4?xRwtiQQfC%_w%p+7+EP(NoE3P0Ff+dtaN z13*~oG(apM(7!An&%Myjy1#=F9l#IjY`>q9`#(HeKR<&gB0$|s06&@pS3cnU8^Dmm z^gl`I`oHc~@V}EvIy@Zuioa+G3&8tB%Re3g_CFmf!N2Jq)qg+S&;URTTFXCLB&0vu zx5YoU6tq7x^h!TL4L!j0J_x|dpw7Q_g)KjM7tp^A12MkEB=^1;jdwp9r-(oJwxhoW zw01wI#2r2@#8*EyQ`0|OjwnF*aXP>!nUTL|-3GuVZ#YS^^DJL@37UZ~z8v-;zg+QNKa{!gKO-I*z@9%NK*irXKz&FMz&2+UK+3KOK(!quzW%R> Qzp?m@z5@{w43ky@GOU;MyZ`_I delta 526 zcmV+p0`dKb1&9TZSATmP-#@?o8$j;5^FN(5*}vU&b-x^j1Hfrl3cr;b7(GID{JyY| zj=ybF{XZxeGQD~3`#<&uBt8}7WWVl@zde#0>%YEJWOXl~96*+36+r29q`rHG zsJ_(w20$%4y+1BAEI>qMO25b9jK6&(u0I&Y?>{TH7C;d~1Ajj&yBfgU)-^zs{Kr4X zZ5F@@cL_ib1YN&nme9XEISjv2{XIb0UI{?H4=uosjJeOg^ba$@w;r9p?$eXMj5Fdtf6OO9DeVKmwtt=&K(qetzshzMKa~loKLUXn zKVX5UziZ_Nz$bDXzXl}qKVt};zDp*%KHt$Hyr=gUJfa)HzmE)nK0W=mzSHs)z>=!a zzN%ETKStEtKWQcFzmgoMzasJ>z>ds!KS|g$K#O~-KVEq2zXoF}K-{g+zsV0NK&xE{ Qz|=0.5.0") + +def get_plugin_pattern() -> gs.GraphPattern: + """ + Toy plugin pattern: + A B + \ / + C, attrs['x'] < 2.0 + / \ + D E + """ + pattern = gs.GraphPattern() + in_0 = pattern.variable() + in_1 = pattern.variable() + a_out = pattern.add("Anode", "A", inputs=[in_0]) + b_out = pattern.add("Bnode", "B", inputs=[in_1]) + check_function = lambda node : node.attrs["x"] < 2.0 + c_out = pattern.add("Cnode", "C", inputs=[a_out, b_out], check_func=check_function) + d_out = pattern.add("Dnode", "D", inputs=[c_out]) + e_out = pattern.add("Enode", "E", inputs=[c_out]) + pattern.set_output_tensors([d_out, e_out]) + + return pattern + +def get_plugin_attributes(sg) -> dict: + """ + example plugin attribute mapping, where the plugin has attribute ToyX, which gets its value from C.x * 2 + """ + return {"ToyX": int(sg.get("Cnode").attrs["x"]) * 2} + diff --git a/tools/Polygraphy/tests/models/qdq_conv.exclude_list.txt b/tools/Polygraphy/tests/models/qdq_conv.exclude_list.txt new file mode 100644 index 00000000..5667c431 --- /dev/null +++ b/tools/Polygraphy/tests/models/qdq_conv.exclude_list.txt @@ -0,0 +1 @@ +Weights_0 diff --git a/tools/Polygraphy/tests/models/qdq_conv.onnx b/tools/Polygraphy/tests/models/qdq_conv.onnx new file mode 100644 index 0000000000000000000000000000000000000000..52581b05a64854d643eb7070309f94abdc03d82e GIT binary patch literal 5462 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%0Dbt~#9C)oJ-4bJ21YmHIk_cSzSC-n$Z_%_yza4C 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 C4lo4( literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/tests/models/weightless.conv.onnx b/tools/Polygraphy/tests/models/weightless.conv.onnx new file mode 100644 index 0000000000000000000000000000000000000000..29ea0f6c2c065882d5fe1249393c574582d4b544 GIT binary patch literal 178 zcmdWy-~v$i#Fd<%R~DaNS`u#{#R6h0u{Z-sEm1C>?9`&X)SUR@ zjKqRe2NnmG1&r)M;`w=b74hjsi3J(OrA6tf`FRkImI4P0hX4?>2rxP^CV4R=@rMM3 z#D}|jx_gB9xCRI7M@a*16cXbS;b0UJ;9}xn1Y!`)0m49cAUQ;c1tjmp!o?uK3jkhE BCx8F| literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/tests/models/weightless.matmul.bf16.onnx b/tools/Polygraphy/tests/models/weightless.matmul.bf16.onnx new file mode 100644 index 0000000000000000000000000000000000000000..70c86768d9f62f9bc6065fd60b8d904be5f2d7fe GIT binary patch literal 151 zcmdk@c$t}%^&o3>BH;`h3@RZnm6H9zcbA-h6^YSX< z(~A-dGKx!!(o^&EARH|j4gn4h0Rbn*Brk>}{*a)M_;6QGcaIPs*Wh6NC}E)CLVR32 d90EcdTudAwERZC^g<_Ww3rN(7g^NLe7XXhPBlG|O literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/tests/models/weightless.matmul.fp16.onnx b/tools/Polygraphy/tests/models/weightless.matmul.fp16.onnx new file mode 100644 index 0000000000000000000000000000000000000000..66b469da9df270e1c598339b9c31ac8d9bbb061d GIT binary patch literal 151 zcmdk@c$t}%^&o3>BH;`h3@RZnm6H9zcbA-h6^YSX< z(~A-dGKx!!(o^&EARH|j4gn4h0WK%TBrk>}{*a)M_;6QGcaIPs*Wh6NC}E)CLVR32 e99%*iTudAwERZC^g<_Ww3s98HiG_7SQBoXlAjFlNpH~LrlGFtQ7YWBM8#1X_w5j2z5B*u*Hn=)_pz#gN1w5)=|2 z?&|685#r+-9IUS;&A|+mgN3pe4+8^(0|P??5HE1h5(i2$IiZBS7juZj5iJd{u|T7N bdJ!(iVt$ek7m{~`Sh$!t7@b(S7zB6$PjQyz literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/tests/models/weightless.sparse.conv.onnx b/tools/Polygraphy/tests/models/weightless.sparse.conv.onnx new file mode 100644 index 0000000000000000000000000000000000000000..ba4c06af16337ffecb9b7c439fa8deaec7904b95 GIT binary patch literal 212 zcmd&o=N-W5zR46VjN>9ztQz$4Z%}bSHuv*K=Wy-~v$i#Fd<%R~DaN zS`u#{#R6h0u{Z-sEm1C>?9`&X)SUR@jKqRe2NnmG1&r)M;`w=b74cwWp@u>@T3Q?| z90EYhBEaaxnB>KfBpDJE5+CmB>FyEY;~E^S9~|Hq6zm#r6mJqG4YXTGj7x-rQAmJ` biGvY{K{N*l16_pVBq0`%yb}u-g8(l8_fRxs literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/tests/models/weightless.sparse.matmul.onnx b/tools/Polygraphy/tests/models/weightless.sparse.matmul.onnx new file mode 100644 index 0000000000000000000000000000000000000000..6ad60884adeed438e8bced4f7af78f98522d2752 GIT binary patch literal 185 zcmd&o=N-W5zR46VjN>9ztQz$4Z%}bSHu$sikrNYIS$i#FLv?l3SV+ zpI=%MZy?16;VH5CCYJb?<_L-B=jBzzgH3^&0pVz=aR_j52rxP^CV4R=NrnW4#D}|j zx_gB9xCRI72M0I?1-r%@#hXM41FaF_~zn^=-tngiw;O0hwBN^HJ~ zCBCIOLgM*(c@^>LMTrF&#id2*srh*jj+PdO00)NvqZ7J$UJOb6AwePW;jW(U9w9!i p!NK}b!a$3K__%mD7=<{vm^eUKAW4J^#bzNEpeUmg3m1a`F91&#FQWhe literal 0 HcmV?d00001 diff --git a/tools/Polygraphy/tests/requirements.txt b/tools/Polygraphy/tests/requirements.txt index 9eb340d0..3cafbb11 100644 --- a/tools/Polygraphy/tests/requirements.txt +++ b/tools/Polygraphy/tests/requirements.txt @@ -1,7 +1,7 @@ colored flaky==3.7.0 numpy==1.21.6 -onnx_graphsurgeon>=0.3.27 +onnx_graphsurgeon>=0.5.0 onnx==1.14.0 onnxconverter_common==1.12.2 onnxmltools==1.11.1 @@ -18,3 +18,4 @@ tensorflow<2.0; python_version<'3.8' tf2onnx torch==2.0.1 wheel +pyyaml diff --git a/tools/Polygraphy/tests/test_examples.py b/tools/Polygraphy/tests/test_examples.py index 60e3089c..a500e230 100644 --- a/tools/Polygraphy/tests/test_examples.py +++ b/tools/Polygraphy/tests/test_examples.py @@ -32,14 +32,18 @@ class Marker: def __init__(self, matches_start_func=None, matches_end_func=None): - self.matches_start = util.default(matches_start_func, lambda line: line == self.start) + self.matches_start = util.default( + matches_start_func, lambda line: line == self.start + ) self.matches_end = util.default(matches_end_func, lambda line: line == self.end) @staticmethod def from_name(name): return Marker( - matches_start_func=lambda line: line == f"", - matches_end_func=lambda line: line == f"", + matches_start_func=lambda line: line + == f"", + matches_end_func=lambda line: line + == f"", ) @@ -95,7 +99,9 @@ def is_in(self, marker): Whether we are currently on a line between the specified start and end marker. This will always return False for a line containing the marker itself. """ - return marker in self.active_markers and not (self.entering(marker) or self.exiting(marker)) + return marker in self.active_markers and not ( + self.entering(marker) or self.exiting(marker) + ) def entering(self, marker): return marker in self.entering_markers @@ -125,8 +131,12 @@ def load_command_blocks_from_readme(readme) -> List[CommandBlock]: with open(readme, "r") as f: contents = f.read() # Check that the README has all the expected sections. - assert "## Introduction" in contents, "All example READMEs should have an 'Introduction' section!" - assert "## Running The Example" in contents, "All example READMEs should have a 'Running The Example' section!" + assert ( + "## Introduction" in contents + ), "All example READMEs should have an 'Introduction' section!" + assert ( + "## Running The Example" in contents + ), "All example READMEs should have a 'Running The Example' section!" cmd_blocks = [] with MarkerTracker(readme) as tracker: @@ -135,7 +145,9 @@ def load_command_blocks_from_readme(readme) -> List[CommandBlock]: continue if tracker.entering(VALID_MARKERS["command"]): - current_block = CommandBlock(xfail=tracker.is_in(VALID_MARKERS["xfail"])) + current_block = CommandBlock( + xfail=tracker.is_in(VALID_MARKERS["xfail"]) + ) elif tracker.exiting(VALID_MARKERS["command"]): cmd_blocks.append(copy.copy(current_block)) elif tracker.is_in(VALID_MARKERS["command"]): @@ -152,7 +164,11 @@ def __init__(self, path_components, artifact_names=[]): self.original_files = [] def _get_file_list(self): - return [path for path in glob.iglob(os.path.join(self.path, "*")) if "__pycache__" not in path] + return [ + path + for path in glob.iglob(os.path.join(self.path, "*")) + if "__pycache__" not in path + ] def _remove_artifacts(self, must_exist=True): for artifact in self.artifacts: @@ -176,14 +192,22 @@ def __enter__(self): def run(self, cmd_block, sandboxed_install_run): # Remove whitespace args and escaped newlines - command = [arg for arg in str(cmd_block).strip().split(" ") if arg.strip() and arg != "\\\n"] + command = [ + arg + for arg in str(cmd_block).strip().split(" ") + if arg.strip() and arg != "\\\n" + ] status = sandboxed_install_run(command, cwd=self.path) details = f"Note: Command was: {' '.join(command)}.\n==== STDOUT ====\n{status.stdout}\n==== STDERR ====\n{status.stderr}" if cmd_block.xfail: - assert not status.success, f"Command that was expected to fail did not fail. {details}" + assert ( + not status.success + ), f"Command that was expected to fail did not fail. {details}" else: - assert status.success, f"Command that was expected to succeed did not succeed. {details}" + assert ( + status.success + ), f"Command that was expected to succeed did not succeed. {details}" return status def __exit__(self, exc_type, exc_value, traceback): @@ -201,13 +225,21 @@ def __str__(self): API_EXAMPLES = [ Example(["api", "00_inference_with_tensorrt"], artifact_names=["identity.engine"]), - Example(["api", "01_comparing_frameworks"], artifact_names=["inference_results.json"]), + Example( + ["api", "01_comparing_frameworks"], artifact_names=["inference_results.json"] + ), Example(["api", "02_validating_on_a_dataset"]), Example(["api", "03_interoperating_with_tensorrt"]), - Example(["api", "04_int8_calibration_in_tensorrt"], artifact_names=["identity-calib.cache"]), + Example( + ["api", "04_int8_calibration_in_tensorrt"], + artifact_names=["identity-calib.cache"], + ), Example(["api", "05_using_tensorrt_network_api"]), Example(["api", "06_immediate_eval_api"], artifact_names=["identity.engine"]), - Example(["api", "07_tensorrt_and_dynamic_shapes"], artifact_names=["dynamic_identity.engine"]), + Example( + ["api", "07_tensorrt_and_dynamic_shapes"], + artifact_names=["dynamic_identity.engine"], + ), Example( ["api", "08_working_with_run_results_and_saved_inputs_manually"], artifact_names=["inputs.json", "outputs.json"], @@ -220,7 +252,8 @@ def __str__(self): @pytest.mark.script_launch_mode("subprocess") def test_api_examples(example, sandboxed_install_run): if "07_tensorrt_and_dynamic_shapes" in example.path and ( - mod.version(trt.__version__) >= mod.version("8.6") and mod.version(trt.__version__) < mod.version("8.7") + mod.version(trt.__version__) >= mod.version("8.6") + and mod.version(trt.__version__) < mod.version("8.7") ): pytest.skip("Broken on 8.6") @@ -244,7 +277,10 @@ def test_api_examples(example, sandboxed_install_run): ["cli", "run", "04_defining_a_tensorrt_network_or_config_manually"], artifact_names=["my_define_network.py", "my_create_config.py"], ), - Example(["cli", "run", "05_comparing_with_custom_input_data"], artifact_names=["custom_inputs.json"]), + Example( + ["cli", "run", "05_comparing_with_custom_input_data"], + artifact_names=["custom_inputs.json"], + ), Example( ["cli", "run", "06_comparing_with_custom_output_data"], artifact_names=["custom_inputs.json", "custom_outputs.json"], @@ -265,7 +301,12 @@ def test_api_examples(example, sandboxed_install_run): pytest.param( Example( ["cli", "convert", "02_deterministic_engine_builds_in_tensorrt"], - artifact_names=["0.engine", "1.engine", "timing.cache", "timing.cache.lock"], + artifact_names=[ + "0.engine", + "1.engine", + "timing.cache", + "timing.cache.lock", + ], ), marks=[pytest.mark.serial, pytest.mark.flaky(max_runs=2)], ), @@ -278,9 +319,14 @@ def test_api_examples(example, sandboxed_install_run): artifact_names=["identity_fp16.onnx", "inputs.json", "outputs_fp32.json"], ), # Surgeon - Example(["cli", "surgeon", "01_isolating_subgraphs"], artifact_names=["subgraph.onnx"]), + Example( + ["cli", "surgeon", "01_isolating_subgraphs"], artifact_names=["subgraph.onnx"] + ), Example(["cli", "surgeon", "02_folding_constants"], artifact_names=["folded.onnx"]), - Example(["cli", "surgeon", "03_modifying_input_shapes"], artifact_names=["dynamic_identity.onnx"]), + Example( + ["cli", "surgeon", "03_modifying_input_shapes"], + artifact_names=["dynamic_identity.onnx"], + ), Example( ["cli", "surgeon", "04_setting_upper_bounds"], artifact_names=["modified.onnx", "folded.onnx"], @@ -306,19 +352,24 @@ def test_api_examples(example, sandboxed_install_run): ), marks=[pytest.mark.slow], ), + # Plugin + Example( + ["cli", "plugin", "01_match_and_replace_plugin"], artifact_names=["config.yaml", "replaced.onnx"] + ), ] @pytest.mark.parametrize("example", CLI_EXAMPLES, ids=lambda case: str(case)) @pytest.mark.script_launch_mode("subprocess") def test_cli_examples(example, sandboxed_install_run): - if "02_deterministic_engine_builds_in_tensorrt" in example.path and mod.version(trt.__version__) < mod.version( - "8.7" - ): + if "02_deterministic_engine_builds_in_tensorrt" in example.path and mod.version( + trt.__version__ + ) < mod.version("8.7"): pytest.skip("Only supported on TensorRT 8.7 and newer") if "03_dynamic_shapes_in_tensorrt" in example.path and ( - mod.version(trt.__version__) >= mod.version("8.6") and mod.version(trt.__version__) < mod.version("8.7") + mod.version(trt.__version__) >= mod.version("8.6") + and mod.version(trt.__version__) < mod.version("8.7") ): pytest.skip("Broken on TensorRT 8.6") @@ -334,8 +385,13 @@ def test_cli_examples(example, sandboxed_install_run): artifact_names=["dynamic_identity.engine"], ), Example(["cli", "inspect", "03_inspecting_an_onnx_model"]), - Example(["cli", "inspect", "05_inspecting_inference_outputs"], artifact_names=["outputs.json"]), - Example(["cli", "inspect", "06_inspecting_input_data"], artifact_names=["inputs.json"]), + Example( + ["cli", "inspect", "05_inspecting_inference_outputs"], + artifact_names=["outputs.json"], + ), + Example( + ["cli", "inspect", "06_inspecting_input_data"], artifact_names=["inputs.json"] + ), Example( ["cli", "inspect", "08_inspecting_tensorrt_onnx_support"], artifact_names=[ @@ -347,17 +403,39 @@ def test_cli_examples(example, sandboxed_install_run): "polygraphy_capability_dumps", ], ), - Example(["cli", "inspect", "07_inspecting_tactic_replays"], artifact_names=["replay.json"]), - Example(["cli", "check", "01_linting_an_onnx_model"], artifact_names=["report.json"]), + Example( + ["cli", "inspect", "07_inspecting_tactic_replays"], + artifact_names=["replay.json"], + ), + Example( + ["cli", "check", "01_linting_an_onnx_model"], artifact_names=["report.json"] + ), + Example( + ["cli", "inspect", "09_inspecting_tensorrt_static_onnx_support"], + artifact_names=[ + "polygraphy_capability_dumps/results.txt", + # Remove directory when done + "polygraphy_capability_dumps", + ], + ), ] if mod.has_mod("tensorflow"): - CLI_INSPECT_CHECK_EXAMPLES.append(Example(["cli", "inspect", "04_inspecting_a_tensorflow_graph"])) + CLI_INSPECT_CHECK_EXAMPLES.append( + Example(["cli", "inspect", "04_inspecting_a_tensorflow_graph"]) + ) -@pytest.mark.parametrize("example", CLI_INSPECT_CHECK_EXAMPLES, ids=lambda case: str(case)) +@pytest.mark.parametrize( + "example", CLI_INSPECT_CHECK_EXAMPLES, ids=lambda case: str(case) +) @pytest.mark.script_launch_mode("subprocess") def test_cli_inspect_check_examples(example, sandboxed_install_run): + if mod.version(trt.__version__) < mod.version("10.0") and ( + "09_inspecting_tensorrt_static_onnx_support" in example.path + ): + pytest.skip("Parser features not supported in TRT <10.0.") + # Last block should be the expected output, and last command should generate it. with example as blocks: command_blocks, expected_output = blocks[:-1], str(blocks[-1]) @@ -365,9 +443,17 @@ def test_cli_inspect_check_examples(example, sandboxed_install_run): actual_output = example.run(cmd_block, sandboxed_install_run).stdout if mod.version(trt.__version__) >= mod.version("9.0") and ( - "01_inspecting_a_tensorrt_network" in example.path or "02_inspecting_a_tensorrt_engine" in example.path + "01_inspecting_a_tensorrt_network" in example.path + or "02_inspecting_a_tensorrt_engine" in example.path ): - pytest.skip("Output is different for TRT >=9, this test needs to be updated to account for that. ") + pytest.skip( + "Output is different for TRT >=9, this test needs to be updated to account for that. " + ) + + if mod.version(trt.__version__) < mod.version("10.0") and ( + "08_inspecting_tensorrt_onnx_support" in example.path + ): + pytest.skip("Output is different for TRT <10.0.") print(actual_output) @@ -396,11 +482,15 @@ def test_cli_inspect_check_examples(example, sandboxed_install_run): "[I] Loading ", ] - for index, (actual_line, expected_line) in enumerate(zip(actual_lines, expected_lines)): + for index, (actual_line, expected_line) in enumerate( + zip(actual_lines, expected_lines) + ): # Skip whitespace, and lines that include runner names (since those have timestamps) - if expected_line.strip() and all([marker not in expected_line for marker in NON_EXACT_LINE_MARKERS]): + if expected_line.strip() and all( + [marker not in expected_line for marker in NON_EXACT_LINE_MARKERS] + ): print(f"Checking line {index}: {expected_line}") - assert actual_line == expected_line + assert actual_line.strip() == expected_line.strip() DEV_EXAMPLES = [ diff --git a/tools/Polygraphy/tests/tools/args/backend/trt/test_config.py b/tools/Polygraphy/tests/tools/args/backend/trt/test_config.py index 39099b40..32c3943c 100644 --- a/tools/Polygraphy/tests/tools/args/backend/trt/test_config.py +++ b/tools/Polygraphy/tests/tools/args/backend/trt/test_config.py @@ -23,7 +23,12 @@ import pytest import tensorrt as trt from polygraphy import mod, util -from polygraphy.backend.trt import TacticRecorder, TacticReplayData, TacticReplayer, create_network +from polygraphy.backend.trt import ( + TacticRecorder, + TacticReplayData, + TacticReplayer, + create_network, +) from polygraphy.exception import PolygraphyException from polygraphy.tools.args import DataLoaderArgs, ModelArgs, TrtConfigArgs from tests.helper import has_dla @@ -33,7 +38,8 @@ @pytest.fixture() def trt_config_args(): return ArgGroupTestHelper( - TrtConfigArgs(allow_engine_capability=True, allow_tensor_formats=True), deps=[ModelArgs(), DataLoaderArgs()] + TrtConfigArgs(allow_engine_capability=True, allow_tensor_formats=True), + deps=[ModelArgs(), DataLoaderArgs()], ) @@ -45,7 +51,9 @@ def test_create_config(self, trt_config_args): trt_config_args.parse_args([]) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert isinstance(config, trt.IBuilderConfig) @pytest.mark.parametrize( @@ -60,7 +68,7 @@ def test_create_config(self, trt_config_args): (["--precision-constraints", "obey"], "OBEY_PRECISION_CONSTRAINTS"), (["--precision-constraints", "prefer"], "PREFER_PRECISION_CONSTRAINTS"), (["--direct-io"], "DIRECT_IO"), - (["--disable-compilation-cache"], "DISABLE_COMPILATION_CACHE") + (["--disable-compilation-cache"], "DISABLE_COMPILATION_CACHE"), ], ) def test_flags(self, trt_config_args, args, flag): @@ -68,13 +76,17 @@ def test_flags(self, trt_config_args, args, flag): pytest.skip("FP8 support was added in 8.6") if flag == "BF16" and mod.version(trt.__version__) < mod.version("8.7"): pytest.skip("BF16 support was added in 8.7") - if flag == "DISABLE_COMPILATION_CACHE" and mod.version(trt.__version__) < mod.version("9.0"): + if flag == "DISABLE_COMPILATION_CACHE" and mod.version( + trt.__version__ + ) < mod.version("9.0"): pytest.skip("BF16 support was added in 9.0") trt_config_args.parse_args(args) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.get_flag(getattr(trt.BuilderFlag, flag)) @pytest.mark.parametrize( @@ -87,10 +99,15 @@ def test_flags(self, trt_config_args, args, flag): ) def test_engine_capability(self, trt_config_args, engine_capability, expected): trt_config_args.parse_args(["--engine-capability", engine_capability]) - assert str(trt_config_args.engine_capability) == f"trt.EngineCapability.{engine_capability.upper()}" + assert ( + str(trt_config_args.engine_capability) + == f"trt.EngineCapability.{engine_capability.upper()}" + ) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.engine_capability == expected def test_dla(self, trt_config_args): @@ -98,7 +115,9 @@ def test_dla(self, trt_config_args): assert trt_config_args.use_dla builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.default_device_type == trt.DeviceType.DLA if has_dla(): assert config.DLA_core == 0 @@ -107,23 +126,42 @@ def test_calibrator_when_dla(self, trt_config_args): trt_config_args.parse_args(["--use-dla", "--int8"]) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert isinstance(config.int8_calibrator, trt.IInt8EntropyCalibrator2) def test_restricted_flags(self, trt_config_args): trt_config_args.parse_args(["--trt-safety-restricted"]) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.get_flag(getattr(trt.BuilderFlag, "SAFETY_SCOPE")) def test_refittable_flags(self, trt_config_args): trt_config_args.parse_args(["--refittable"]) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.get_flag(getattr(trt.BuilderFlag, "REFIT")) + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("10.0"), + reason="Feature not present before 10.0", + ) + def test_weight_streaming_flags(self, trt_config_args): + trt_config_args.parse_args(["--weight-streaming"]) + builder, network = create_network() + + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: + assert config.get_flag(getattr(trt.BuilderFlag, "WEIGHT_STREAMING")) + @pytest.mark.parametrize( "opt, cls", [ @@ -138,7 +176,9 @@ def test_tactics(self, trt_config_args, opt, cls): trt_config_args.parse_args([opt, f.name]) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: selector = config.algorithm_selector assert selector.make_func == cls assert selector.path == f.name @@ -153,38 +193,70 @@ def test_tactics(self, trt_config_args, opt, cls): (["--tactic-sources", "CUBLAS", "cuDNN"], 5), (["--tactic-sources", "CUBLAS_LT", "CUDNN"], 6), (["--tactic-sources", "CUDNN", "cuBLAS", "CUBLAS_LT"], 7), - (["--tactic-sources", "CUDNN", "cuBLAS", "CUBLAS_LT", "edge_mask_convolutions"], 15), - (["--tactic-sources", "CUDNN", "cuBLAS", "CUBLAS_LT", "edge_mask_convolutions", "jit_convolutions"], 31), + ( + [ + "--tactic-sources", + "CUDNN", + "cuBLAS", + "CUBLAS_LT", + "edge_mask_convolutions", + ], + 15, + ), + ( + [ + "--tactic-sources", + "CUDNN", + "cuBLAS", + "CUBLAS_LT", + "edge_mask_convolutions", + "jit_convolutions", + ], + 31, + ), ] - if mod.version(trt.__version__) >= mod.version("8.7"): + if mod.version(trt.__version__) >= mod.version("10.0"): + TACTIC_SOURCES_CASES[0] = ([], 24) + elif mod.version(trt.__version__) >= mod.version("8.7"): TACTIC_SOURCES_CASES[0] = ([], 29) @pytest.mark.parametrize("opt, expected", TACTIC_SOURCES_CASES) def test_tactic_sources(self, trt_config_args, opt, expected): trt_config_args.parse_args(opt) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.get_tactic_sources() == expected @pytest.mark.skipif( - mod.version(trt.__version__) < mod.version("8.7"), reason="ERROR_ON_TIMING_CACHE_MISS support was added in 8.7" + mod.version(trt.__version__) < mod.version("8.7"), + reason="ERROR_ON_TIMING_CACHE_MISS support was added in 8.7", ) def test_error_on_timing_cache_miss(self, trt_config_args): trt_config_args.parse_args(["--error-on-timing-cache-miss"]) builder, network = create_network() assert trt_config_args.error_on_timing_cache_miss - with builder, network, trt_config_args.create_config(builder, network=network) as config: - assert config.get_flag(getattr(trt.BuilderFlag, "ERROR_ON_TIMING_CACHE_MISS")) + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: + assert config.get_flag( + getattr(trt.BuilderFlag, "ERROR_ON_TIMING_CACHE_MISS") + ) - @pytest.mark.parametrize("base_class", ["IInt8LegacyCalibrator", "IInt8EntropyCalibrator2"]) + @pytest.mark.parametrize( + "base_class", ["IInt8LegacyCalibrator", "IInt8EntropyCalibrator2"] + ) def test_calibration_base_class(self, trt_config_args, base_class): trt_config_args.parse_args(["--int8", "--calibration-base-class", base_class]) assert trt_config_args.calibration_base_class.unwrap() == f"trt.{base_class}" builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert isinstance(config.int8_calibrator, getattr(trt, base_class)) def test_legacy_calibrator_params(self, trt_config_args): @@ -204,7 +276,9 @@ def test_legacy_calibrator_params(self, trt_config_args): assert trt_config_args._regression_cutoff == regression_cutoff builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.int8_calibrator.get_quantile() == quantile assert config.int8_calibrator.get_regression_cutoff() == regression_cutoff @@ -226,7 +300,9 @@ def test_no_deps_profiles_int8(self, trt_config_args): builder, network = create_network() network.add_input("input", shape=(-1, 25, 25), dtype=trt.float32) - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert isinstance(config, trt.IBuilderConfig) # Unfortunately there is no API to check the contents of the profile in a config. # The checks above will have to do. @@ -258,12 +334,16 @@ def my_load_config(config): f.flush() os.fsync(f.fileno()) - trt_config_args.parse_args(["--trt-config-script", f"{f.name}:my_load_config"]) + trt_config_args.parse_args( + ["--trt-config-script", f"{f.name}:my_load_config"] + ) assert trt_config_args.trt_config_script == f.name assert trt_config_args.trt_config_func_name == "my_load_config" builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network) as config: + with builder, network, trt_config_args.create_config( + builder, network + ) as config: assert isinstance(config, trt.IBuilderConfig) assert config.get_flag(trt.BuilderFlag.FP16) @@ -286,12 +366,23 @@ def my_postprocess_config(builder, network, config): f.flush() os.fsync(f.fileno()) - trt_config_args.parse_args(["--trt-config-postprocess-script", f"{f.name}:my_postprocess_config", "--int8"]) + trt_config_args.parse_args( + [ + "--trt-config-postprocess-script", + f"{f.name}:my_postprocess_config", + "--int8", + ] + ) assert trt_config_args.trt_config_postprocess_script == f.name - assert trt_config_args.trt_config_postprocess_func_name == "my_postprocess_config" + assert ( + trt_config_args.trt_config_postprocess_func_name + == "my_postprocess_config" + ) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network) as config: + with builder, network, trt_config_args.create_config( + builder, network + ) as config: assert isinstance(config, trt.IBuilderConfig) assert config.get_flag(trt.BuilderFlag.FP16) assert config.get_flag(trt.BuilderFlag.INT8) @@ -304,9 +395,21 @@ def my_postprocess_config(builder, network, config): ["--int8", "--calibration-base-class", "IInt8LegacyCalibrator)"], ["--int8", "--calibration-base-class", "IInt8LegacyCalibrator}"], ["--int8", "--calibration-base-class", "IInt8LegacyCalibrator]"], - ["--int8", "--calibration-base-class", "IInt8LegacyCalibrator));print(('hi'"], - ["--int8", "--calibration-base-class", "IInt8LegacyCalibrator;print(('hi')"], - ["--int8", "--calibration-base-class", "IInt8LegacyCalibrator';print('hi')"], + [ + "--int8", + "--calibration-base-class", + "IInt8LegacyCalibrator));print(('hi'", + ], + [ + "--int8", + "--calibration-base-class", + "IInt8LegacyCalibrator;print(('hi')", + ], + [ + "--int8", + "--calibration-base-class", + "IInt8LegacyCalibrator';print('hi')", + ], ["--tactic-sources", "CUBLAS, fp16=True"], ], ) @@ -319,16 +422,37 @@ def test_code_injection_checks(self, trt_config_args, args): @pytest.mark.parametrize( "args,expected", [ - (["--pool-limit", "workspace:250"], {trt.MemoryPoolType.WORKSPACE: 250}), - (["--pool-limit", "dla_managed_sram:250"], {trt.MemoryPoolType.DLA_MANAGED_SRAM: 250}), - (["--pool-limit", "dla_local_dram:250"], {trt.MemoryPoolType.DLA_LOCAL_DRAM: 250}), - (["--pool-limit", "dla_global_dram:250"], {trt.MemoryPoolType.DLA_GLOBAL_DRAM: 250}), + ( + ["--pool-limit", "workspace:250"], + {trt.MemoryPoolType.WORKSPACE: 250}, + ), + ( + ["--pool-limit", "dla_managed_sram:250"], + {trt.MemoryPoolType.DLA_MANAGED_SRAM: 250}, + ), + ( + ["--pool-limit", "dla_local_dram:250"], + {trt.MemoryPoolType.DLA_LOCAL_DRAM: 250}, + ), + ( + ["--pool-limit", "dla_global_dram:250"], + {trt.MemoryPoolType.DLA_GLOBAL_DRAM: 250}, + ), # Test case insensitivity - (["--pool-limit", "wOrkSpaCE:250"], {trt.MemoryPoolType.WORKSPACE: 250}), + ( + ["--pool-limit", "wOrkSpaCE:250"], + {trt.MemoryPoolType.WORKSPACE: 250}, + ), # Test works with K/M/G suffixes - (["--pool-limit", "workspace:2M"], {trt.MemoryPoolType.WORKSPACE: 2 << 20}), + ( + ["--pool-limit", "workspace:2M"], + {trt.MemoryPoolType.WORKSPACE: 2 << 20}, + ), # Test works with scientific notation - (["--pool-limit", "workspace:2e3"], {trt.MemoryPoolType.WORKSPACE: 2e3}), + ( + ["--pool-limit", "workspace:2e3"], + {trt.MemoryPoolType.WORKSPACE: 2e3}, + ), ], ) def test_memory_pool_limits(self, args, expected, trt_config_args): @@ -355,9 +479,9 @@ def test_memory_pool_limits_empty_key_not_allowed(self, args, trt_config_args): @pytest.mark.parametrize( "preview_features", [ - [], - ["FASter_DYNAMIC_ShAPeS_0805"], - ["FASter_DYNAMIC_ShAPeS_0805", "DISABLE_EXTERNAL_TACTIC_SOURCES_FOR_CORE_0805"], + ["PROFILE_SHAriNG_0806"] + if mod.version(trt.__version__) >= mod.version("10.0") + else ["FASter_DYNAMIC_ShAPeS_0805"], ], ) def test_preview_features(self, trt_config_args, preview_features): @@ -367,10 +491,14 @@ def test_preview_features(self, trt_config_args, preview_features): sanitized_preview_features = [pf.upper() for pf in preview_features] - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: # Check that only the enabled preview features are on. for name, pf in trt.PreviewFeature.__members__.items(): - assert config.get_preview_feature(pf) == (name in sanitized_preview_features) + assert config.get_preview_feature(pf) == ( + name in sanitized_preview_features + ) @pytest.mark.parametrize( "quantization_flags", @@ -387,13 +515,18 @@ def test_quantization_flags(self, trt_config_args, quantization_flags): sanitized_quantization_flags = [pf.upper() for pf in quantization_flags] - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: # Check that only the enabled quantization flags are on. for name, qf in trt.QuantizationFlag.__members__.items(): - assert config.get_quantization_flag(qf) == (name in sanitized_quantization_flags) + assert config.get_quantization_flag(qf) == ( + name in sanitized_quantization_flags + ) @pytest.mark.skipif( - mod.version(trt.__version__) < mod.version("8.6"), reason="Unsupported for TRT versions prior to 8.6" + mod.version(trt.__version__) < mod.version("8.6"), + reason="Unsupported for TRT versions prior to 8.6", ) @pytest.mark.parametrize("level", range(6)) def test_builder_optimization_level(self, trt_config_args, level): @@ -402,7 +535,9 @@ def test_builder_optimization_level(self, trt_config_args, level): builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.builder_optimization_level == level if mod.version(trt.__version__) >= mod.version("8.6"): @@ -418,16 +553,20 @@ def test_builder_optimization_level(self, trt_config_args, level): def test_hardware_compatibility_level(self, trt_config_args, level, expected): trt_config_args.parse_args(["--hardware-compatibility-level", str(level)]) assert ( - str(trt_config_args.hardware_compatibility_level) == f"trt.HardwareCompatibilityLevel.{expected.name}" + str(trt_config_args.hardware_compatibility_level) + == f"trt.HardwareCompatibilityLevel.{expected.name}" ) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.hardware_compatibility_level == expected @pytest.mark.skipif( - mod.version(trt.__version__) < mod.version("8.6"), reason="Unsupported for TRT versions prior to 8.6" + mod.version(trt.__version__) < mod.version("8.6"), + reason="Unsupported for TRT versions prior to 8.6", ) @pytest.mark.parametrize("num_streams", range(5)) def test_max_aux_streams(self, trt_config_args, num_streams): @@ -436,15 +575,24 @@ def test_max_aux_streams(self, trt_config_args, num_streams): builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.max_aux_streams == num_streams - @pytest.mark.skipif(mod.version(trt.__version__) < mod.version("8.6"), reason="Unsupported before TRT 8.6") + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.6"), + reason="Unsupported before TRT 8.6", + ) @pytest.mark.parametrize( "args, attr, expected_flag", [ (["--version-compatible"], "version_compatible", "VERSION_COMPATIBLE"), - (["--version-compatible", "--exclude-lean-runtime"], "exclude_lean_runtime", "EXCLUDE_LEAN_RUNTIME"), + ( + ["--version-compatible", "--exclude-lean-runtime"], + "exclude_lean_runtime", + "EXCLUDE_LEAN_RUNTIME", + ), ], ) def test_version_compatibility(self, trt_config_args, args, attr, expected_flag): @@ -452,5 +600,33 @@ def test_version_compatibility(self, trt_config_args, args, attr, expected_flag) assert getattr(trt_config_args, attr) builder, network = create_network() - with builder, network, trt_config_args.create_config(builder, network=network) as config: + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: assert config.get_flag(getattr(trt.BuilderFlag, expected_flag)) + + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.6"), + reason="Unsupported before 8.6", + ) + @pytest.mark.parametrize( + "level, expected", + [ + ("none", trt.ProfilingVerbosity.NONE), + ("detailed", trt.ProfilingVerbosity.DETAILED), + ("layer_names_only", trt.ProfilingVerbosity.LAYER_NAMES_ONLY), + ], + ) + def test_profiling_verbosity(self, trt_config_args, level, expected): + trt_config_args.parse_args(["--profiling-verbosity", str(level)]) + assert ( + str(trt_config_args.profiling_verbosity) + == f"trt.ProfilingVerbosity.{expected.name}" + ) + + builder, network = create_network() + + with builder, network, trt_config_args.create_config( + builder, network=network + ) as config: + assert config.profiling_verbosity == expected diff --git a/tools/Polygraphy/tests/tools/args/backend/trt/test_loader.py b/tools/Polygraphy/tests/tools/args/backend/trt/test_loader.py index 87a52117..9c967532 100644 --- a/tools/Polygraphy/tests/tools/args/backend/trt/test_loader.py +++ b/tools/Polygraphy/tests/tools/args/backend/trt/test_loader.py @@ -50,7 +50,8 @@ class TestTrtLoadNetworkArgs: @pytest.mark.parametrize("force_onnx_loader", [True, False]) @pytest.mark.parametrize( "opts,expected_flag", - [([], None)] + [(["--strongly-typed"], trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)] + [([], None)] + + [(["--strongly-typed"], trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)] if mod.version(trt.__version__) >= mod.version("8.7") else [], ) @@ -78,7 +79,9 @@ def test_load_network(self, force_onnx_loader, opts, expected_flag): builder, network, _ = arg_group.load_network() with builder, network: assert network.num_outputs == 1 - assert network.get_output(0).name == ("identity_out_0" if force_onnx_loader else "identity_out_2") + assert network.get_output(0).name == ( + "identity_out_0" if force_onnx_loader else "identity_out_2" + ) if expected_flag is not None: assert network.get_flag(expected_flag) @@ -112,7 +115,13 @@ def {func_name}(network): else: pps_arg = f"{f.name}:{func_name}" - arg_group.parse_args([ONNX_MODELS["identity_identity"].path, "--trt-network-postprocess-script", pps_arg]) + arg_group.parse_args( + [ + ONNX_MODELS["identity_identity"].path, + "--trt-network-postprocess-script", + pps_arg, + ] + ) builder, network, _ = arg_group.load_network() with builder, network: @@ -235,7 +244,9 @@ def test_set_tensor_formats(self): assert network.get_input(0).allowed_formats == ( 1 << int(trt.TensorFormat.LINEAR) | 1 << int(trt.TensorFormat.CHW4) ) - assert network.get_output(0).allowed_formats == 1 << int(trt.TensorFormat.HWC8) + assert network.get_output(0).allowed_formats == 1 << int( + trt.TensorFormat.HWC8 + ) def test_set_tensor_formats_default_disallowed(self): arg_group = ArgGroupTestHelper( @@ -257,13 +268,23 @@ def test_set_tensor_formats_default_disallowed(self): ] ) - @pytest.mark.skipif(mod.version(trt.__version__) < mod.version("8.6"), reason="API was added in TRT 8.6") - @pytest.mark.parametrize("args", [["--hardware-compatibility-level=ampere_plus"], ["--version-compatible"]]) + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.6"), + reason="API was added in TRT 8.6", + ) + @pytest.mark.parametrize( + "args", + [["--hardware-compatibility-level=ampere_plus"], ["--version-compatible"]], + ) def test_onnx_flags_autoenabled_for_vc_or_hc(self, args): - arg_group = ArgGroupTestHelper(TrtOnnxFlagArgs(), deps=[ModelArgs(), TrtConfigArgs()]) + arg_group = ArgGroupTestHelper( + TrtOnnxFlagArgs(), deps=[ModelArgs(), TrtConfigArgs()] + ) arg_group.parse_args([ONNX_MODELS["identity_identity"].path] + args) - assert arg_group.get_flags() == [make_trt_enum_val("OnnxParserFlag", "NATIVE_INSTANCENORM")] + assert arg_group.get_flags()[0] == [ + make_trt_enum_val("OnnxParserFlag", "NATIVE_INSTANCENORM") + ] @pytest.fixture() @@ -284,11 +305,12 @@ def engine_loader_args(): class TestTrtEngineLoaderArgs: def test_build_engine(self, engine_loader_args): - engine_loader_args.parse_args([ONNX_MODELS["identity_identity"].path, "--trt-outputs=identity_out_0"]) + engine_loader_args.parse_args( + [ONNX_MODELS["identity_identity"].path, "--trt-outputs=identity_out_0"] + ) with engine_loader_args.load_engine() as engine: assert isinstance(engine, trt.ICudaEngine) - assert len(engine) == 2 assert engine[1] == "identity_out_0" def test_build_engine_custom_network(self, engine_loader_args): @@ -300,9 +322,10 @@ def test_build_engine_custom_network(self, engine_loader_args): out.name = "output" network.mark_output(out) - with builder, network, engine_loader_args.load_engine(network=(builder, network)) as engine: + with builder, network, engine_loader_args.load_engine( + network=(builder, network) + ) as engine: assert isinstance(engine, trt.ICudaEngine) - assert len(engine) == 2 assert engine[0] == "input" assert engine[1] == "output" @@ -317,12 +340,17 @@ def test_load_serialized_engine(self, engine_loader_args): engine_loader_args.parse_args([f.name, "--model-type=engine"]) with engine_loader_args.load_engine() as engine: assert isinstance(engine, trt.ICudaEngine) - assert len(engine) == 2 + assert engine[0] == "x" assert engine[1] == "y" - @pytest.mark.skipif(mod.version(trt.__version__) < mod.version("8.6"), reason="API was added in TRT 8.6") - def test_load_engine_with_custom_runtime(self, engine_loader_args, nvinfer_lean_path): + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("8.6"), + reason="API was added in TRT 8.6", + ) + def test_load_engine_with_custom_runtime( + self, engine_loader_args, nvinfer_lean_path + ): with util.NamedTemporaryFile() as f, engine_bytes_from_network( network_from_onnx_path(ONNX_MODELS["identity"].path), CreateConfig(version_compatible=True, exclude_lean_runtime=True), @@ -331,11 +359,12 @@ def test_load_engine_with_custom_runtime(self, engine_loader_args, nvinfer_lean_ f.flush() os.fsync(f.fileno()) - engine_loader_args.parse_args([f.name, "--model-type=engine", "--load-runtime", nvinfer_lean_path]) + engine_loader_args.parse_args( + [f.name, "--model-type=engine", "--load-runtime", nvinfer_lean_path] + ) assert engine_loader_args.load_runtime == nvinfer_lean_path with engine_loader_args.load_engine() as engine: assert isinstance(engine, trt.ICudaEngine) - assert len(engine) == 2 with TrtRunner(engine) as runner: assert runner.infer({"x": np.ones((1, 1, 2, 2), dtype=np.float32)}) diff --git a/tools/Polygraphy/tests/tools/args/comparator/test_data_loader.py b/tools/Polygraphy/tests/tools/args/comparator/test_data_loader.py index a5a56af5..7c4dac90 100644 --- a/tools/Polygraphy/tests/tools/args/comparator/test_data_loader.py +++ b/tools/Polygraphy/tests/tools/args/comparator/test_data_loader.py @@ -58,6 +58,8 @@ class TestDataLoaderArgs: (["--iterations=12"], ["iterations"], [12]), (["--val-range", "[0.0,inf]"], ["val_range"], [{"": (0.0, float("inf"))}]), (["--val-range", "[-inf,0.0]"], ["val_range"], [{"": (float("-inf"), 0.0)}]), + (["--data-loader-backend-module", "torch"], ["data_loader_backend_module"], ["torch"]), + (["--data-loader-backend-module", "numpy"], ["data_loader_backend_module"], ["numpy"]), ], ids=lambda c: c[1][0], ) diff --git a/tools/Polygraphy/tests/tools/conftest.py b/tools/Polygraphy/tests/tools/conftest.py index 4a3e6c92..508e7155 100644 --- a/tools/Polygraphy/tests/tools/conftest.py +++ b/tools/Polygraphy/tests/tools/conftest.py @@ -30,7 +30,9 @@ def make_poly_fixture(subtool: List[str]): @pytest.fixture() def poly_fixture(script_runner): - def poly_fixture_impl(additional_opts: List[str] = [], expect_error: bool = False, *args, **kwargs): + def poly_fixture_impl( + additional_opts: List[str] = [], expect_error: bool = False, *args, **kwargs + ): cmd = ["polygraphy"] + subtool + ["-v"] + additional_opts # NOTE: script_runner does not work very well in `in-process`` mode if you need to inspect stdout/stderr. # Occasionally, the output comes out empty - not clear why. Cave emptor! @@ -43,7 +45,6 @@ def poly_fixture_impl(additional_opts: List[str] = [], expect_error: bool = Fals return poly_fixture - poly = make_poly_fixture([]) poly_run = make_poly_fixture(["run"]) poly_convert = make_poly_fixture(["convert"]) @@ -54,9 +55,13 @@ def poly_fixture_impl(additional_opts: List[str] = [], expect_error: bool = Fals poly_template = make_poly_fixture(["template"]) poly_debug = make_poly_fixture(["debug"]) poly_data = make_poly_fixture(["data"]) +poly_plugin_match = make_poly_fixture(["plugin", "match"]) +poly_plugin_list_plugins = make_poly_fixture(["plugin", "list"]) +poly_plugin_replace = make_poly_fixture(["plugin", "replace"]) - -FakeAlgorithmContext = namedtuple("FakeAlgorithmContext", ["name", "num_inputs", "num_outputs"]) +FakeAlgorithmContext = namedtuple( + "FakeAlgorithmContext", ["name", "num_inputs", "num_outputs"] +) FakeAlgorithm = namedtuple("FakeAlgorithm", ["algorithm_variant", "io_info"]) FakeAlgorithm.get_algorithm_io_info = lambda this, index: this.io_info[index] @@ -66,20 +71,30 @@ def poly_fixture_impl(additional_opts: List[str] = [], expect_error: bool = Fals @pytest.fixture(scope="session", params=["", "subdir"]) def replay_dir(request): def fake_context(name, num_inputs=1, num_outputs=1): - return FakeAlgorithmContext(name=name, num_inputs=num_inputs, num_outputs=num_outputs) + return FakeAlgorithmContext( + name=name, num_inputs=num_inputs, num_outputs=num_outputs + ) def fake_algo( - implementation=6, tactic=0, num_io=2, tensor_format=trt.TensorFormat.LINEAR, dtype=trt.float32, strides=(1, 2) + implementation=6, tactic=0, num_io=2, dtype=trt.float32, strides=(1, 2) ): io_info = [ TensorInfo( - tensor_format=tensor_format, dtype=dtype, strides=strides, vectorized_dim=-1, components_per_element=1 + dtype=dtype, + strides=strides, + vectorized_dim=-1, + components_per_element=1, ) ] * num_io - return FakeAlgorithm(algorithm_variant=FakeAlgorithmVariant(implementation, tactic), io_info=io_info) + return FakeAlgorithm( + algorithm_variant=FakeAlgorithmVariant(implementation, tactic), + io_info=io_info, + ) def make_replay(tactic): - return TacticReplayData().add("layer0", Algorithm.from_trt(fake_context("layer0"), fake_algo(0, tactic))) + return TacticReplayData().add( + "layer0", Algorithm.from_trt(fake_context("layer0"), fake_algo(0, tactic)) + ) with tempfile.TemporaryDirectory() as dir: @@ -104,7 +119,7 @@ def make_path(prefix, *args): [I] Loaded {num} bad tactic replays. [I] Found potentially bad tactics: [I] Layer: layer0 - Algorithms: ['(Implementation: 0, Tactic: 2) | Inputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1, 2), -1, 1),) | Outputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1, 2), -1, 1),)'] + Algorithms: ['(Implementation: 0, Tactic: 2) | Inputs: (TensorInfo(DataType.FLOAT, (1, 2), -1, 1),) | Outputs: (TensorInfo(DataType.FLOAT, (1, 2), -1, 1),)'] """ ) yield dir, EXPECTED_OUTPUT diff --git a/tools/Polygraphy/tests/tools/test_check.py b/tools/Polygraphy/tests/tools/test_check.py index 8c48c698..a3b27a85 100644 --- a/tools/Polygraphy/tests/tools/test_check.py +++ b/tools/Polygraphy/tests/tools/test_check.py @@ -296,7 +296,7 @@ def test_custom_op(self, poly_check): expect_error=False, ) condition = ( - lambda entry: Lint.CUSTOM_OP_EXCEPTION_SUBSTR in entry["message"] + lambda entry: any([substr in entry["message"] for substr in Lint.CUSTOM_OP_EXCEPTION_SUBSTRS]) and entry["source"] == Lint.Source.ONNXRUNTIME.value and entry["level"] == Lint.Level.WARNING.value ) diff --git a/tools/Polygraphy/tests/tools/test_inspect.py b/tools/Polygraphy/tests/tools/test_inspect.py index b7b5982b..953c21b3 100644 --- a/tools/Polygraphy/tests/tools/test_inspect.py +++ b/tools/Polygraphy/tests/tools/test_inspect.py @@ -28,7 +28,6 @@ engine_from_network, network_from_onnx_bytes, save_engine, - util as trt_util, ) from tests.models.meta import ONNX_MODELS, TF_MODELS @@ -70,7 +69,9 @@ def check_lines_match(actual, expected, should_check_line=lambda x: True): actual = [ line for line in actual.splitlines() - if "Loading" not in line and not line.startswith("[V]") and not line.startswith("[W]") + if "Loading" not in line + and not line.startswith("[V]") + and not line.startswith("[W]") ] expected = expected.splitlines() assert len(actual) == len(expected) @@ -461,7 +462,9 @@ def check_lines_match(actual, expected, should_check_line=lambda x: True): class TestInspectModel: - @pytest.mark.parametrize("case", ONNX_CASES, ids=lambda case: f"{case[0]}-{case[1]}") + @pytest.mark.parametrize( + "case", ONNX_CASES, ids=lambda case: f"{case[0]}-{case[1]}" + ) def test_onnx(self, case, poly_inspect): model, show, expected, additional_opts = case status = poly_inspect( @@ -519,7 +522,9 @@ def load_network(builder, network): @pytest.mark.flaky(max_runs=3) def test_trt_engine(self, case, dynamic_identity_engine, poly_inspect): show, expected = case - status = poly_inspect(["model", dynamic_identity_engine] + (["--show"] + show if show else [])) + status = poly_inspect( + ["model", dynamic_identity_engine] + (["--show"] + show if show else []) + ) expected = dedent(expected).strip() actual = "\n".join(status.stdout.splitlines()[1:]) # Ignore loading message @@ -592,7 +597,9 @@ def test_num_items(self, poly_run, poly_inspect, num_items): lines = [ line.strip() for line in status.stdout.splitlines() - if line.strip() and line.startswith(constants.TAB * 2) and line.strip() != "..." + if line.strip() + and line.startswith(constants.TAB * 2) + and line.strip() != "..." ] for line in lines: items = [e for e in line.strip("[]").split() if "..." not in e] @@ -608,16 +615,16 @@ def test_num_items(self, poly_run, poly_inspect, num_items): "pow_scalar", r""" [I] Layer: (Unnamed Layer* 0) [Shuffle] - Algorithm: (Implementation: 2147483661, Tactic: 0) | Inputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (), -1, 1),) | Outputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1,), -1, 1),) + Algorithm: (Implementation: 2147483661, Tactic: 0) | Inputs: (TensorInfo(DataType.FLOAT, (), -1, 1),) | Outputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1),) Layer: node_of_z - Algorithm: (Implementation: 2147483651, Tactic: 1) | Inputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1,), -1, 1), TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1,), -1, 1)) | Outputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1,), -1, 1),) + Algorithm: (Implementation: 2147483651, Tactic: 1) | Inputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1), TensorInfo(DataType.FLOAT, (1,), -1, 1)) | Outputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1),) """ if mod.version(trt.__version__) < mod.version("8.7") else r""" [I] Layer: ONNXTRT_Broadcast - Algorithm: (Implementation: 2147483661, Tactic: 0) | Inputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (), -1, 1),) | Outputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1,), -1, 1),) + Algorithm: (Implementation: 2147483661, Tactic: 0) | Inputs: (TensorInfo(DataType.FLOAT, (), -1, 1),) | Outputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1),) Layer: PWN(node_of_z) - Algorithm: (Implementation: 2147483688, Tactic: 1) | Inputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1,), -1, 1), TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1,), -1, 1)) | Outputs: (TensorInfo(TensorFormat.LINEAR, DataType.FLOAT, (1,), -1, 1),) + Algorithm: (Implementation: 2147483688, Tactic: 1) | Inputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1), TensorInfo(DataType.FLOAT, (1,), -1, 1)) | Outputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1),) """, ], ] @@ -667,6 +674,15 @@ def test_show_tactics(self, case, poly_run, poly_inspect): ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- FAKE! | 2 | In node 0 (importFallbackPluginImporter): UNSUPPORTED_NODE: Assertion failed: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[0, 1], [2, 3]] FAKER! | 1 | In node 0 (importFallbackPluginImporter): UNSUPPORTED_NODE: Assertion failed: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[4, 5]] + """ + if mod.version(trt.__version__) < mod.version("10.0") + else """ + [I] ===== Summary ===== + Operator | Count | Reason | Nodes + -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + FAKE! | 1 | In node 0 with name: Fake1 and operator: FAKE! (checkFallbackPluginImporter): INVALID_NODE: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[0, 1]] + FAKE! | 1 | In node 0 with name: Fake2 and operator: FAKE! (checkFallbackPluginImporter): INVALID_NODE: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[2, 3]] + FAKER! | 1 | In node 0 with name: Fake3 and operator: FAKER! (checkFallbackPluginImporter): INVALID_NODE: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[4, 5]] """, ), ( @@ -688,6 +704,7 @@ def test_capability(self, case, poly_inspect): status = poly_inspect( [ "capability", + "--with-partitioning", ONNX_MODELS[model].path, "-o", os.path.join(outdir, "subgraphs"), @@ -699,7 +716,7 @@ def test_capability(self, case, poly_inspect): glob.glob(os.path.join(outdir, "subgraphs", "**")), ) ) == sorted(expected_files) - assert dedent(expected_summary).strip() in status.stdout + assert dedent(expected_summary).strip() in dedent(status.stdout).strip() class TestDiffTactics: @@ -737,7 +754,9 @@ def find_file(dirpath, filename): class TestInspectSparsity: - @pytest.mark.parametrize("model_name", ["matmul", "matmul.bf16", "matmul.bf16.i32data", "conv"]) + @pytest.mark.parametrize( + "model_name", ["matmul", "matmul.bf16", "matmul.bf16.i32data", "conv"] + ) def test_prune_check(self, poly_inspect, model_name): with tempfile.TemporaryDirectory() as outdir: ipath = ONNX_MODELS[model_name].path diff --git a/tools/Polygraphy/tests/tools/test_plugin.py b/tools/Polygraphy/tests/tools/test_plugin.py new file mode 100644 index 00000000..a74f0b17 --- /dev/null +++ b/tools/Polygraphy/tests/tools/test_plugin.py @@ -0,0 +1,108 @@ +# +# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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 pytest +import os +import yaml +from tests.models.meta import ONNX_MODELS +import onnx +import tempfile + + +class TestMatch: + + TOY_MODEL_PATH = ONNX_MODELS["graph_with_subgraph_matching_toy_plugin"].path + PLUGINS_PATH = os.path.join(os.path.dirname(TOY_MODEL_PATH), "plugins") + + @pytest.mark.script_launch_mode("subprocess") + def test_match_toy(self, poly_plugin_match): + + assert os.path.exists(self.TOY_MODEL_PATH) + + with tempfile.TemporaryDirectory() as outdir: + config_yaml_loc = os.path.join(outdir, "config.yaml") + poly_plugin_match( + [ + self.TOY_MODEL_PATH, + "--plugin-dir", + self.PLUGINS_PATH, + "-o", + config_yaml_loc, + ] + ) + + assert os.path.exists(config_yaml_loc) + + with open(config_yaml_loc, "r") as stream: + config_yaml = yaml.safe_load_all(stream) + + num_plugins = 0 + for plugin in config_yaml: + num_plugins += 1 + assert plugin["name"] == "toyPlugin" + assert len(plugin["instances"]) == 1 + assert len(plugin["instances"][0]["inputs"]) == 2 + assert len(plugin["instances"][0]["outputs"]) == 2 + assert plugin["instances"][0]["attributes"]["ToyX"] == 2 + + assert num_plugins == 1 + + @pytest.mark.script_launch_mode("subprocess") + def test_match_list_toy(self, poly_plugin_list_plugins): + status = poly_plugin_list_plugins( + [self.TOY_MODEL_PATH, "--plugin-dir", self.PLUGINS_PATH] + ) + + assert "{'toyPlugin': 1}" in status.stdout + + @pytest.mark.script_launch_mode("subprocess") + def test_replace_toy(self, poly_plugin_replace, poly_plugin_match): + with tempfile.TemporaryDirectory() as outdir: + config_yaml_loc = os.path.join(outdir, "config.yaml") + + poly_plugin_match( + [ + self.TOY_MODEL_PATH, + "--plugin-dir", + self.PLUGINS_PATH, + "-o", + config_yaml_loc, + ] + ) + + replaced_loc = os.path.join(outdir, "replaced.onnx") + poly_plugin_replace( + [ + self.TOY_MODEL_PATH, + "--plugin-dir", + self.PLUGINS_PATH, + "--config", + config_yaml_loc, + "-o", + replaced_loc, + ] + ) + + model = onnx.load(replaced_loc) + assert len(model.graph.node) == 2 + node_names = {node.name for node in model.graph.node} + + assert "n1" in node_names + assert not node_names.intersection({"n2", "n3", "n4", "n5", "n6"}) + assert model.graph.node[1].op_type == "toyPlugin" + assert model.graph.node[1].attribute[0].name == "ToyX" + assert model.graph.node[1].attribute[0].i == 2 diff --git a/tools/Polygraphy/tests/tools/test_run.py b/tools/Polygraphy/tests/tools/test_run.py index fe02e319..0e5e4a69 100644 --- a/tools/Polygraphy/tests/tools/test_run.py +++ b/tools/Polygraphy/tests/tools/test_run.py @@ -21,6 +21,7 @@ import sys import tempfile from textwrap import dedent +import tensorrt as trt import onnx import pytest @@ -207,6 +208,41 @@ def test_multiple_profiles(self, poly_run, optimization_profile): poly_run(cmd) + @pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("10.0"), + reason="Feature not present before 10.0", + ) + @pytest.mark.parametrize("allocation_strategy", [None, "static", "profile", "runtime"]) + def test_allocation_strategies(self, poly_run, allocation_strategy): + cmd = [ + ONNX_MODELS["residual_block"].path, + "--trt", + "--onnxrt", + # Profile 0 + "--trt-min-shapes", + "gpu_0/data_0:[1,3,224,224]", + "--trt-opt-shapes", + "gpu_0/data_0:[1,3,224,224]", + "--trt-max-shapes", + "gpu_0/data_0:[2,3,224,224]", + # Profile 1 + "--trt-min-shapes", + "gpu_0/data_0:[1,3,224,224]", + "--trt-opt-shapes", + "gpu_0/data_0:[1,3,224,224]", + "--trt-max-shapes", + "gpu_0/data_0:[4,3,224,224]", + # Input shapes + "--input-shapes", + "gpu_0/data_0:[2,3,224,224]", + "--optimization-profile", + "1", + ] + if allocation_strategy is not None: + cmd += ["--allocation-strategy", allocation_strategy] + + poly_run(cmd) + def test_int8_calibration_cache(self, poly_run): with util.NamedTemporaryFile() as outpath: cmd = [ONNX_MODELS["identity"].path, "--trt", "--int8", "--calibration-cache", outpath.name] diff --git a/tools/Polygraphy/tests/tools/test_surgeon.py b/tools/Polygraphy/tests/tools/test_surgeon.py index 3054f0be..88a4ab61 100644 --- a/tools/Polygraphy/tests/tools/test_surgeon.py +++ b/tools/Polygraphy/tests/tools/test_surgeon.py @@ -18,12 +18,14 @@ import tempfile import onnx +from onnx import numpy_helper import onnx_graphsurgeon as gs import pytest from polygraphy import util from polygraphy.backend.onnx import util as onnx_util +from polygraphy.tools.sparse import SparsityPruner from tests.helper import is_file_non_empty -from tests.models.meta import ONNX_MODELS +from tests.models.meta import ONNX_MODELS, model_path @pytest.fixture() @@ -33,6 +35,67 @@ def onnx_model_sanity_check_impl(model_path): return onnx_model_sanity_check_impl +def get_exclude_list(exclude_list): + if not exclude_list: + return set() + with open(exclude_list) as fp: + lines = [line.rstrip() for line in fp] + return set(lines) + +def pruned_initializer_sanity_check(opath, is_sparse=False, exclude_list=None): + exclude_list = get_exclude_list(exclude_list) + # we only prune the input data of QuantizeLinear and leave the scale and zero_point untouched + if 'qdq' in opath: + exclude_list.add('y_scale') + exclude_list.add('y_zero_point') + + model = onnx.load(opath) + for initializer in model.graph.initializer: + # If initializer is to be left un-stripped + if initializer.name in exclude_list: + # ensure initializer is non-empty and doc_string doesn't contain the weightless flag + shape_match = list(numpy_helper.to_array(initializer).shape) == initializer.dims + if "TRT_WEIGHTLESS" in initializer.doc_string or not shape_match: + return False + continue + + # ensure initializer is empty and doc_string is in required format + init_empty = initializer.raw_data == b"" + trt_weightless, sparsity = initializer.doc_string.split('/') + trt_weightless_correctness = trt_weightless == "TRT_WEIGHTLESS" + sparsity_correctness = False + if (not is_sparse and sparsity == "") or (is_sparse and sparsity == "SPARSE_2_4"): + sparsity_correctness = True + + if not (init_empty and trt_weightless_correctness and sparsity_correctness): + return False + + return True + +def get_initializers_to_sparsify(ipath): + model = onnx.load(ipath) + initializers_to_sparsify = set() + for initializer in model.graph.initializer: + if "SPARSE_2_4" in initializer.doc_string: + initializers_to_sparsify.add(initializer.name) + + return initializers_to_sparsify + +def reconstructed_initializer_sanity_check(opath, initializers_to_sparsify): + model = onnx.load(opath) + sparsity_checker = SparsityPruner(model) + sparsity_checker.check() + sparse_tensors = sparsity_checker.sparse_tensors + for initializer in model.graph.initializer: + shape_match = list(numpy_helper.to_array(initializer).shape) == initializer.dims + if not shape_match: + return False + + # ensure sparsity of initializers is retained + if initializer.name in initializers_to_sparsify and initializer.name not in sparse_tensors: + return False + + return True def was_shape_inference_run(status, model): logging_correct = "Shape inference completed successfully" in (status.stdout + status.stderr) @@ -560,3 +623,47 @@ def test_prune(self, poly_surgeon, onnx_model_sanity_check, model_name): assert status if "bf16" not in ipath: onnx_model_sanity_check(opath) + +class TestSurgeonWeightStrip: + @pytest.mark.parametrize("model_name", ["matmul", "matmul.fp16", "matmul.bf16", "conv", "sparse.matmul", "sparse.conv", + "transpose_matmul", "qdq_conv"]) + def test_weight_strip(self, poly_surgeon, model_name): + with tempfile.TemporaryDirectory() as outdir: + ipath = ONNX_MODELS[model_name].path + opath = os.path.join(outdir, "weightless." + os.path.basename(ipath)) + status = poly_surgeon(["weight-strip", ipath, "-o", opath]) + assert status + + is_sparse = "sparse" in ipath + assert pruned_initializer_sanity_check(opath, is_sparse=is_sparse) + + @pytest.mark.parametrize( + "model_name, exclude_list", [ + ["matmul", "matmul.exclude_list.txt"], + ["sparse.conv", "sparse.conv.exclude_list.txt"], + ["qdq_conv", "qdq_conv.exclude_list.txt"]]) + def test_weight_strip_exclude_file(self, poly_surgeon, model_name, exclude_list): + with tempfile.TemporaryDirectory() as outdir: + ipath = ONNX_MODELS[model_name].path + exclude_list = model_path(exclude_list) + opath = os.path.join(outdir, "weightless_sparse." + os.path.basename(ipath)) + status = poly_surgeon(["weight-strip", ipath, "-o", opath, "--exclude-list", exclude_list]) + assert status + + is_sparse = "sparse" in ipath + assert pruned_initializer_sanity_check(opath, is_sparse=is_sparse, exclude_list=exclude_list) + +class TestSurgeonWeightReconstruct: + @pytest.mark.parametrize("model_name", ["weightless.matmul.fp16", "weightless.matmul.bf16", "weightless.conv", "weightless.sparse.matmul", + "weightless.sparse.conv", "weightless.transpose_matmul", "weightless.qdq_conv"]) + def test_weight_reconstruct(self, poly_surgeon, onnx_model_sanity_check, model_name): + with tempfile.TemporaryDirectory() as outdir: + ipath = ONNX_MODELS[model_name].path + opath = os.path.join(outdir, "reconstruct." + os.path.basename(ipath)) + status = poly_surgeon(["weight-reconstruct", ipath, "-o", opath]) + assert status + if "bf16" not in ipath: + onnx_model_sanity_check(opath) + + initializers_to_sparsify = get_initializers_to_sparsify(ipath) + assert reconstructed_initializer_sanity_check(opath, initializers_to_sparsify) \ No newline at end of file diff --git a/tools/Polygraphy/tests/util/test_serde.py b/tools/Polygraphy/tests/util/test_serde.py index e47486ff..19dcc8bf 100644 --- a/tools/Polygraphy/tests/util/test_serde.py +++ b/tools/Polygraphy/tests/util/test_serde.py @@ -120,10 +120,10 @@ def make_algo(): tactic=5, # Should work even if strides are not set inputs=[ - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2), -1, 1), - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2), -1, 1), + TensorInfo(trt.float32, (1, 2), -1, 1), + TensorInfo(trt.float32, (1, 2), -1, 1), ], - outputs=[TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (2, 3), -1, 1)], + outputs=[TensorInfo(trt.float32, (2, 3), -1, 1)], ) @@ -148,21 +148,21 @@ class TestImplementations: @pytest.mark.parametrize( "obj", [ - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1), + TensorInfo(trt.float32, (1, 2, 3), -1, 1), Algorithm( implementation=4, tactic=5, - inputs=[TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1)], - outputs=[TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1)], + inputs=[TensorInfo(trt.float32, (1, 2, 3), -1, 1)], + outputs=[TensorInfo(trt.float32, (1, 2, 3), -1, 1)], ), Algorithm( implementation=4, tactic=5, inputs=[ - TensorInfo(trt.TensorFormat.LINEAR, trt.float32, (1, 2, 3), -1, 1), - TensorInfo(trt.TensorFormat.CHW32, trt.int8, (1, 2, 3), -1, 1), + TensorInfo(trt.float32, (1, 2, 3), -1, 1), + TensorInfo(trt.int8, (1, 2, 3), -1, 1), ], - outputs=[TensorInfo(trt.TensorFormat.CHW32, trt.float16, (1, 2, 3), -1, 1)], + outputs=[TensorInfo(trt.float16, (1, 2, 3), -1, 1)], ), np.ones((3, 4, 5), dtype=np.int64), np.ones(5, dtype=np.int64), @@ -170,7 +170,9 @@ class TestImplementations: np.random.random_sample((3, 5)), torch.ones((3, 4, 5), dtype=torch.int64), make_iter_result(), - RunResults([("runner0", [make_iter_result()]), ("runner0", [make_iter_result()])]), + RunResults( + [("runner0", [make_iter_result()]), ("runner0", [make_iter_result()])] + ), ], ids=lambda x: type(x), )