A back door possibility is to use lsof to show what files and sockets are open on the server, and post-process to link socket processes to files to file size or extension. Of course, if there is a multithreaded server process, it gets fuzzy again.
Finding a way to throttle sockets that have moved too many bytes might be nasty to persistent HTTP1.1 users, a kind of usage that is friendlier to the server than file per connection, never mind ftp's two connections. If you could serve one packet out per socket in strict rotation, the long connections for big files have less effect on the new ones. Optimally, you want the oldest connection to get done, so you have fewer concurrent connections and least redo activity, yet you want the new connections to be favored for a while, since they may want little. If you can move aging connections to the long pool tagged with their start time, you can favor the young connections and allow the oldest to take from their excess bandwidth. You may have more bandwidth than the oldest connection, so what it cannot use of that excess goes to the second oldest, and so on.
I am not sure what tools facilitate this, or if any tool can tag HTTP1.1 persistent connections and tell if such a connection is not doing large files.
Packet size is maxed out for every transfer with more than MTU to move at that moment, but MTU is characteristically only 1500, not much of a challenge to fill.
Large files served out might have their sockets tagged, or be segregated on a server that is allowed less priority to the network.
Uploads are really hard, since you cannot look at the file in advance. Extensions and even Content-type: are not very reliable, easily subverted for download/upload by a zip of temporary rename. Files may be encoded to make them not sniffable for type signatures.
Just spitballing!