[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ImageBitmap as input of fromPixel for tfjs webgpu #3660

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

shaoboyan
Copy link
Contributor
@shaoboyan shaoboyan commented Jul 27, 2020

This patch support imageBitmap as input type of fromPixel. By calling
copyImageBitmapToTexture to do upload.


This change is Reviewable

@shaoboyan
Copy link
Contributor Author

@qjia7 Please do first round review, thanks!

@shaoboyan
Copy link
Contributor Author

Per @qjia7's suggestion, will update this patch to leverage FromPixelProgram mechanism.

@shaoboyan
Copy link
Contributor Author

@qjia7 Update the patch and please do first round review if you have time!


setOutput(floor(value * 255.0 + 0.5));
void main() {
ivec3 coords = getOutputCoordsIB();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can directly use getCoordsFromFlatIndex(int(gl_GlobalInvocationID.x) * outShape.numChannels) function in shader_preprocessor.ts and don't need to define a new one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I can have a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works fine! Done.


if (pixels instanceof ImageBitmap) {
const output = this.makeOutputArray(outShape, 'int32');
if (!this.psoForIBCompiled) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use if (! this.fromPixelProgram)? So you can remove posForIBCompiled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// buffer
this.fromPixelProgram.setUniform(this.device, uniformData);

this.fromPixelProgram.setProperties(bindGroupLayout, pipeline);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to setWebGPUBinary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

GPUTexture {
if (!this.inputTexture || this.lastPixelSize.width !== pixelWidth ||
this.lastPixelSize.height !== pixelHeight) {
this.inputTexture = device.createTexture({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to care when to dispose this texture?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good question. Per my opnion, this texture won't be reused in another program in this patch original design. Based on this, I'd like to leave the destruct of this inputTexture (and the uniform buffer) for js object lifecycle. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you can rely on the js object lifecycle to manage GPU textures - but maybe I'm missing something - cc @kainino0x

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops nevermind kai just saw that you already addressed this - reviewable and github reviews don't work together sigh

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kainino0x @annxingyuan Thanks for inputs.
I refer to the sample here https://github.com/austinEng/webgpu-samples/blob/master/src/examples/texturedCube.ts and I think maybe leave the destruct task for GC is Ok(because we won't recycle them). But as Kai said, GC indeed cannot understand the VRAM pressure. I'll make a place to destruct them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. (By adding dispose logic).

@qjia7
Copy link
Collaborator
qjia7 commented Aug 3, 2020

+@annxingyuan @kainino0x take a look.

Copy link
Contributor
@kainino0x kainino0x left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 1 approvals obtained (waiting on @annxingyuan, @qjia7, and @shaoboyan)


tfjs-backend-webgpu/src/backend_webgpu.ts, line 1186 at r2 (raw file):

          !(!(pixels instanceof ImageBitmap) &&
            (pixels.data instanceof Uint8Array)) &&
          !(pixels instanceof ImageBitmap)) {

I think if you do pixels instanceof ImageBitmap first then it shouldn't be necessary to inline the !(pixels instanceof ImageBitmap) && in the line above. (Maybe I'm misreading; this expression is really hard to read now that it's nested like this.)

      if (!(pixels instanceof HTMLVideoElement) &&
          !(pixels instanceof HTMLImageElement) &&
          !(pixels instanceof HTMLCanvasElement) &&
          !(pixels instanceof ImageData) &&
          !(pixels instanceof ImageBitmap) &&
          !(pixels.data instanceof Uint8Array)) {

tfjs-backend-webgpu/src/backend_webgpu.ts, line 1190 at r2 (raw file):

            'pixels passed to tf.browser.fromPixels() must be either an ' +
            `HTMLVideoElement, HTMLImageElement, HTMLCanvasElement, ImageData` +
            ` or {data: Uint32Array, width: number, height: number}, ` +

Hm, this says Uint32Array but the code says Uint8Array?


tfjs-backend-webgpu/src/kernels/from_pixels_webgpu.ts, line 101 at r1 (raw file):

Previously, shaoboyan (Yan, Shaobo) wrote…

A good question. Per my opnion, this texture won't be reused in another program in this patch original design. Based on this, I'd like to leave the destruct of this inputTexture (and the uniform buffer) for js object lifecycle. WDYT?

If there is a place where you can destroy it, it would be best - garbage collection is not very reliable right now for WebGPU objects because the GC doesn't understand VRAM pressure (so it just looks like a small wrapper object). But if there's no place, it's fine to let it GC.


tfjs-backend-webgpu/src/kernels/from_pixels_webgpu.ts, line 51 at r2 (raw file):

      int size;
      int width;
      int numChannels;} outShape;

nit: } should be on the next line.


tfjs-backend-webgpu/src/kernels/from_pixels_webgpu.ts, line 72 at r2 (raw file):

    }
    `;
    this.shaderKey = 'fromPixel';

Needs to include the workGroupSize


tfjs-backend-webgpu/src/kernels/from_pixels_webgpu.ts, line 89 at r2 (raw file):

  }

  getInputTexture(device: GPUDevice, pixelWidth: number, pixelHeight: number):

nit: since this creates a new object, it should say something like "create" or "make" instead of just "get".


tfjs-backend-webgpu/src/kernels/from_pixels_webgpu.ts, line 108 at r2 (raw file):

  }

  generateEncoder(device: GPUDevice, output: GPUBuffer, uniformData: number[]):

nit: Seems this only handles arrays of length 3. Can the type of uniformData be constrained further to [number, number, number]?


tfjs-backend-webgpu/src/kernels/from_pixels_webgpu.ts, line 114 at r2 (raw file):

         uniformData[1] !== this.lastUniformContent[1] ||
         uniformData[2] !== this.lastUniformContent[2])) {
      // TODO(tfjs-optimization): using new mapAsync API to update the buffer.

fyi, If this data is small (seems that it is) then writeBuffer would probably be fine. But you would have to implement the queueing yourself (to make sure it is called at the right time relative to the submits). (Though same with mapAsync if you're writing into the buffer directly instead of using a staging buffer like setUniform does now.)


tfjs-backend-webgpu/src/kernels/from_pixels_webgpu.ts, line 144 at r2 (raw file):

    passEncoder.setBindGroup(0, bindGroup);
    passEncoder.dispatch(this.dispatch[0], this.dispatch[1], this.dispatch[2]);
    // passEncoder.dispatch(Math.ceil(size / numChannels / workGroupSize));

nit: remove commented out code

numChannels: number): Tensor3D {
if (pixels == null) {
throw new Error(
'pixels passed to tf.browser.fromPixels() can not be null');
}

// TODO(tfjs-optimization): These has chance to be cached and do not update
// buffer always.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain this comment a little bit more?

Copy link
Contributor Author
@shaoboyan shaoboyan Aug 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing!
Sry this is the outdated TODO and I've move the related logic into the kernel. Will remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

!(pixels.data instanceof Uint8Array)) {
!(!(pixels instanceof ImageBitmap) &&
(pixels.data instanceof Uint8Array)) &&
!(pixels instanceof ImageBitmap)) {
throw new Error(
'pixels passed to tf.browser.fromPixels() must be either an ' +
`HTMLVideoElement, HTMLImageElement, HTMLCanvasElement, ImageData` +
` or {data: Uint32Array, width: number, height: number}, ` +
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or ImageBitmap

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

let imageData = (pixels as ImageData | backend_util.PixelData).data;

if (env().getBool('IS_BROWSER')) {
if (!(pixels instanceof HTMLVideoElement) &&
!(pixels instanceof HTMLImageElement) &&
!(pixels instanceof HTMLCanvasElement) &&
!(pixels instanceof ImageData) &&
!(pixels.data instanceof Uint8Array)) {
!(!(pixels instanceof ImageBitmap) &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to add another && condition to the pixels.data instanceof Uint8Array check?

Copy link
Contributor Author
@shaoboyan shaoboyan Aug 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sry, I realized that the input could be the raw uint8Array. Added it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

this.glslang, this.device, this.fromPixelProgram, [], output);

// TODO(tfjs-optimization): May use direct number instead of uniform
// buffer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by use direct number?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means the same way as current workGroupSize did (and generate multiple shaders)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modify the comments.


constructor(outputShape: number[]) {
this.outputShape = outputShape;
this.dispatchLayout = flatDispatchLayout(this.outputShape);
this.dispatch = computeDispatch(this.dispatchLayout, this.outputShape);

const workGroupSize = 256;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious - how did you arrive at this number?

Copy link
Contributor Author
@shaoboyan shaoboyan Aug 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, it is the experimental value (Jiajia provided some possible values and we did some experiments) :p

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good :) Could you include a brief comment that you empirically determined this to be the best value?

int texC = coords[1];
int depth = coords[2];
vec4 values = imageLoad(srcImage, ivec2(texC, texR));
float value;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can value exist only in the scope of the for loop instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

`;
this.shaderKey = 'fromPixel';
}

setWebGPUBinary(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense for these methods (setwebgpubinary and setuniform) to exist on the backend interface instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best place to put these logic is compileAndRun. I think setWebGPUBinary and generateEncoder are only introduced by fromPixel only due to compileAndRun doesn't support texture as input.
I think there is no other ops can share these logic. So I'd like to keep them here until compileAndRun can support texture as input. I'll use a CL to remove them . WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

GPUTexture {
if (!this.inputTexture || this.lastPixelSize.width !== pixelWidth ||
this.lastPixelSize.height !== pixelHeight) {
this.inputTexture = device.createTexture({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you can rely on the js object lifecycle to manage GPU textures - but maybe I'm missing something - cc @kainino0x

this.uniform = uniformBuffer;
}

getInputTexture(device: GPUDevice, pixelWidth: number, pixelHeight: number):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a webgpu buffer manager (buffer_manager.ts) that takes care of recycling buffers whenever possible and allocating new buffers - maybe we could introduce a similar mechanism for textures

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agree. But for fromPixel, the inputTexture won't be created/recycle in high frequency. (And I think other ops don't use texture) So I think maybe for fromPixels, we can keep this simple management WDYT?

return this.inputTexture;
}

generateEncoder(device: GPUDevice, output: GPUBuffer, uniformData: number[]):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again would it be possible to move this logic into the backend so it can be shared?

@shaoboyan shaoboyan force-pushed the webgpu branch 2 times, most recently from 3bac731 to 2cece04 Compare August 5, 2020 11:15
@shaoboyan
Copy link
Contributor Author

@kainino0x sry I don't know how to reply to your comments (there is no comment text area). But I think all comments should have been addressed in latest patch.
@kainino0x @annxingyuan address most comments, PTAL again, thanks!

@kainino0x
Copy link
Contributor

@kainino0x sry I don't know how to reply to your comments (there is no comment text area). But I think all comments should have been addressed in latest patch.

You can do it by opening Reviewable (the link is in the PR description). But no need, thanks for the revision! I'm really busy now, but will take a look when I can.

@kainino0x
Copy link
Contributor

(No need to wait for me to rereview before merging, though, if it's ready.)

Copy link
Collaborator
@annxingyuan annxingyuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @shaoboyan ! Just left one last tiny comment and then I will merge!

@shaoboyan shaoboyan force-pushed the webgpu branch 2 times, most recently from 9a31949 to d0f856c Compare August 7, 2020 02:09
inputTexture: GPUTexture = null;
lastPixelSize = {width: 0, height: 0};

private disposed = false;

constructor(outputShape: number[]) {
this.outputShape = outputShape;
this.dispatchLayout = flatDispatchLayout(this.outputShape);
this.dispatch = computeDispatch(this.dispatchLayout, this.outputShape);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry that I didn't notice it earlier. But your dispatch should be like below. With this change, I think your shader should be accelerated a lot.

this.workGroupSize = [256, 1, 1]; // Maybe other values. Please do another round of experiments.
this.workPerThread = numChannels;
this.dispatch = computeDispatch(this.dispatchLayout, this.outputShape, this.workGroupSize, [this.workPerThread, 1 , 1]);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -26,35 +26,144 @@ export class FromPixelsProgram implements WebGPUProgram {
variableNames = ['A'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

['A'] is not needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. And by assign variableNames to [], we don't need the change in shader_preprocessor file.

@shaoboyan shaoboyan force-pushed the webgpu branch 3 times, most recently from 63c4ad1 to 35f1649 Compare August 10, 2020 09:45
This patch support imageBitmap as input type of fromPixel. By calling
copyImageBitmapToTexture to do upload.
@qjia7
Copy link
Collaborator
qjia7 commented Aug 11, 2020

LGTM. Thanks.

@shaoboyan
Copy link
Contributor Author

@annxingyuan @qjia7 I don't think the bots error is related to this patch because the build fail log here https://console.cloud.google.com/cloud-build/builds/bd30c441-a3f5-4941-8607-ac52057f34a9;step=5?project=learnjs-174218
says:
Chrome 84.0.4147 (Mac OS X 10.12.6) ERROR
Disconnected, because no message in 30000 ms.
Am I right?

axinging added a commit to axinging/tfjs-models that referenced this pull request Aug 19, 2020
Currently async fromxPixels is not done in different backend.
For WebGPU backend, the WIP PR is tensorflow/tfjs#3660.
axinging added a commit to axinging/tfjs-models that referenced this pull request Sep 25, 2020
Currently async fromxPixels is not done in different backend.
For WebGPU backend, the WIP PR is tensorflow/tfjs#3660.
axinging added a commit to axinging/tfjs-models that referenced this pull request Sep 25, 2020
Currently async fromxPixels is not done in different backend.
For WebGPU backend, the WIP PR is tensorflow/tfjs#3660.
axinging added a commit to axinging/tfjs-models that referenced this pull request Sep 27, 2020
Currently async fromxPixels is not done in different backend.
For WebGPU backend, the WIP PR is tensorflow/tfjs#3660.
axinging added a commit to axinging/tfjs-models that referenced this pull request Sep 27, 2020
Currently async fromxPixels is not done in different backend.
For WebGPU backend, the WIP PR is tensorflow/tfjs#3660.
axinging added a commit to axinging/tfjs-models that referenced this pull request Sep 28, 2020
Currently async fromxPixels is not done in different backend.
For WebGPU backend, the WIP PR is tensorflow/tfjs#3660.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants