Instructions to use litert-community/Idol with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use litert-community/Idol with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Download python/deform_conv.py from litert-community/Idol: direct link, hf CLI and curl.
- Browser
- Download file 4.95 kB
-
https://huggingface.co/litert-community/Idol/resolve/main/python/deform_conv.py
- Command line
-
hf download hf://litert-community/Idol/python/deform_conv.py
-
curl -L -o deform_conv.py https://huggingface.co/litert-community/Idol/resolve/main/python/deform_conv.py
4.95 kB
| """torchvision.ops.deform_conv2d (modulated, stride 1, dilation 1, one offset group) in plain tensor ops that the LiteRT | |
| converter lowers (GATHER on the token axis, elementwise bilinear weights, one matmul), for a CPU graph of BiRefNet's | |
| decoder: the GPU has no gather, so the dense deformable convolution is a GPU wall (memory project_birefnet_park). | |
| Same arithmetic as torchvision: for output pixel (h, w) and kernel tap k = (i, j), sample the input at | |
| (h - pad + i + dy_k, w - pad + j + dx_k) bilinearly, corners outside the image count as 0, multiply by mask_k, then | |
| contract over (tap, channel) with the weight.""" | |
| import torch | |
| import torch.nn.functional as F | |
| def deform_conv2d_gather(x, offset, weight, bias, padding, mask, max_bytes=None, lean=False, row0=0): | |
| """max_bytes: run the output rows in tiles so that no gathered [taps, rows, width, channels] float32 tensor passes | |
| it (the CPU's peak memory; each output element keeps the same arithmetic). None: one tile. | |
| lean: the corner-validity mask in min / max arithmetic (exact: the corner coordinates are integers) and the row | |
| lookup as an embedding lookup (EMBEDDING_LOOKUP, no bounds-check ops: the indices are clamped), so the CPU runs | |
| two ops per corner outside XNNPACK (the cast to int and the lookup) instead of about ten. | |
| row0: offset and mask hold the output rows [row0, row0 + their height) only (a row tile of the whole output; the | |
| input x is the whole map).""" | |
| b, c, h, w = x.shape | |
| o, _, kh, kw = weight.shape | |
| k = kh * kw | |
| ph, pw = (padding, padding) if isinstance(padding, int) else padding | |
| ho, wo = offset.shape[2], w + 2 * pw - kw + 1 | |
| dev = x.device | |
| # base sampling grid per tap: [k, ho, wo] | |
| gy = torch.arange(ho, device=dev, dtype=x.dtype).view(1, ho, 1) + (row0 - ph) | |
| gx = torch.arange(wo, device=dev, dtype=x.dtype).view(1, 1, wo) - pw | |
| ki = torch.arange(kh, device=dev, dtype=x.dtype).repeat_interleave(kw).view(k, 1, 1) | |
| kj = torch.arange(kw, device=dev, dtype=x.dtype).repeat(kh).view(k, 1, 1) | |
| off = offset.view(b, k, 2, ho, wo) | |
| msk = mask.view(b, k, ho, wo) | |
| tokens = x.permute(0, 2, 3, 1).reshape(b * h * w, c) # token-major input: row = (batch, y, x) | |
| base = (torch.arange(b, device=dev) * (h * w)).view(b, 1, 1, 1) | |
| if lean: | |
| base = base.to(x.dtype) | |
| wmat = weight.permute(2, 3, 1, 0).reshape(k * c, o) # weight[o, c, i, j] -> [(i, j, c), o] | |
| rows = ho if max_bytes is None else max(1, min(ho, int(max_bytes) // (b * k * wo * c * 4))) | |
| outs = [] | |
| for r0 in range(0, ho, rows): | |
| r1 = min(ho, r0 + rows) | |
| n = r1 - r0 | |
| py = gy[:, r0:r1] + ki + off[:, :, 0, r0:r1] # [b, k, n, wo] | |
| px = gx + kj + off[:, :, 1, r0:r1] | |
| y0 = torch.floor(py) | |
| x0 = torch.floor(px) | |
| ly, lx = py - y0, px - x0 | |
| acc = 0 | |
| for dy, dx, wgt in ((0, 0, (1 - ly) * (1 - lx)), (0, 1, (1 - ly) * lx), (1, 0, ly * (1 - lx)), (1, 1, ly * lx)): | |
| yy, xx = y0 + dy, x0 + dx | |
| if lean: | |
| # 1 inside, 0 outside; relu(t) - relu(t - 1) = clamp(t, 0, 1) without RELU_0_TO_1 (no XNNPACK kernel) | |
| u = lambda t: F.relu(t) - F.relu(t - 1) # noqa: E731 | |
| valid = u(yy + 1) * u(h - yy) * u(xx + 1) * u(w - xx) | |
| idx = (yy.clamp(0, h - 1) * w + xx.clamp(0, w - 1) + base).to(torch.int64) # [b, k, n, wo] | |
| v = F.embedding(idx.reshape(-1), tokens).view(b, k, n, wo, c) | |
| else: | |
| valid = ((yy >= 0) & (yy <= h - 1) & (xx >= 0) & (xx <= w - 1)).to(x.dtype) | |
| idx = (yy.clamp(0, h - 1) * w + xx.clamp(0, w - 1)).to(torch.int64) + base # [b, k, n, wo] | |
| v = torch.index_select(tokens, 0, idx.reshape(-1)).view(b, k, n, wo, c) | |
| acc = acc + v * (wgt * valid * msk[:, :, r0:r1]).unsqueeze(-1) | |
| cols = acc.permute(0, 2, 3, 1, 4).reshape(b * n * wo, k * c) # (tap, channel) per output pixel | |
| outs.append((cols @ wmat).view(b, n, wo, o)) | |
| out = (outs[0] if len(outs) == 1 else torch.cat(outs, dim=1)).permute(0, 3, 1, 2) | |
| if bias is not None: | |
| out = out + bias.view(1, o, 1, 1) | |
| return out | |
| if __name__ == '__main__': | |
| from torchvision.ops import deform_conv2d | |
| torch.manual_seed(0) | |
| for ks, pad in ((1, 0), (3, 1), (7, 3)): | |
| x = torch.randn(1, 64, 33, 40) | |
| off = torch.randn(1, 2 * ks * ks, 33, 40) * 4 | |
| m = torch.rand(1, ks * ks, 33, 40) * 2 | |
| wt = torch.randn(256, 64, ks, ks) * 0.05 | |
| ref = deform_conv2d(x, off, wt, None, padding=pad, mask=m) | |
| got = deform_conv2d_gather(x, off, wt, None, pad, m) | |
| tiled = deform_conv2d_gather(x, off, wt, None, pad, m, max_bytes=ks * ks * 40 * 64 * 4 * 5, lean=True) | |
| print(f'k={ks}: max |diff| {(ref - got).abs().max():.3e} tiled lean vs one tile {(tiled - got).abs().max():.3e} ' | |
| f'max |ref| {ref.abs().max():.3e}') | |